mirror of
https://github.com/Unleash/unleash.git
synced 2025-08-27 13:49:10 +02:00
chore: Patch 4.22.6 (#3603)
<!-- 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! ❤️ --> Includes: - Stickiness options from context fields. - Docs update about project mode and project stickiness ## 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 # <!-- (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
3698f1d2ca
commit
03d3ee76d1
1
.github/workflows/e2e.frontend.yaml
vendored
1
.github/workflows/e2e.frontend.yaml
vendored
@ -11,7 +11,6 @@ jobs:
|
||||
- groups/groups.spec.ts
|
||||
- projects/access.spec.ts
|
||||
- projects/overview.spec.ts
|
||||
- projects/settings.spec.ts
|
||||
- projects/notifications.spec.ts
|
||||
- segments/segments.spec.ts
|
||||
- import/import.spec.ts
|
||||
|
@ -1,12 +1,11 @@
|
||||
import Select from 'component/common/select';
|
||||
import { SelectChangeEvent, useTheme } from '@mui/material';
|
||||
import useUnleashContext from 'hooks/api/getters/useUnleashContext/useUnleashContext';
|
||||
const builtInStickinessOptions = [
|
||||
{ key: 'default', label: 'default' },
|
||||
{ key: 'userId', label: 'userId' },
|
||||
{ key: 'sessionId', label: 'sessionId' },
|
||||
{ key: 'random', label: 'random' },
|
||||
];
|
||||
|
||||
type OptionType = { key: string; label: string };
|
||||
|
||||
const DEFAULT_RANDOM_OPTION = 'random';
|
||||
const DEFAULT_STICKINESS_OPTION = 'default';
|
||||
|
||||
interface IStickinessSelectProps {
|
||||
label: string;
|
||||
@ -25,20 +24,27 @@ export const StickinessSelect = ({
|
||||
const { context } = useUnleashContext();
|
||||
const theme = useTheme();
|
||||
|
||||
const resolveStickinessOptions = () =>
|
||||
builtInStickinessOptions.concat(
|
||||
context
|
||||
.filter(contextDefinition => contextDefinition.stickiness)
|
||||
.filter(
|
||||
contextDefinition =>
|
||||
!builtInStickinessOptions.find(
|
||||
builtInStickinessOption =>
|
||||
builtInStickinessOption.key ===
|
||||
contextDefinition.name
|
||||
)
|
||||
)
|
||||
.map(c => ({ key: c.name, label: c.name }))
|
||||
);
|
||||
const resolveStickinessOptions = () => {
|
||||
const options = context
|
||||
.filter(field => field.stickiness)
|
||||
.map(c => ({ key: c.name, label: c.name })) as OptionType[];
|
||||
|
||||
if (
|
||||
!options.find(option => option.key === 'default') &&
|
||||
!context.find(field => field.name === DEFAULT_STICKINESS_OPTION)
|
||||
) {
|
||||
options.push({ key: 'default', label: 'default' });
|
||||
}
|
||||
|
||||
if (
|
||||
!options.find(option => option.key === 'random') &&
|
||||
!context.find(field => field.name === DEFAULT_RANDOM_OPTION)
|
||||
) {
|
||||
options.push({ key: 'random', label: 'random' });
|
||||
}
|
||||
|
||||
return options;
|
||||
};
|
||||
|
||||
const stickinessOptions = resolveStickinessOptions();
|
||||
return (
|
||||
|
@ -227,6 +227,16 @@ class FeatureToggleService {
|
||||
'You can not change the featureName for an activation strategy.',
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
strategy.parameters &&
|
||||
'stickiness' in strategy.parameters &&
|
||||
strategy.parameters.stickiness === ''
|
||||
) {
|
||||
throw new InvalidOperationError(
|
||||
'You can not have an empty string for stickiness.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async validateProjectCanAccessSegments(
|
||||
@ -411,6 +421,14 @@ class FeatureToggleService {
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
strategyConfig.parameters &&
|
||||
'stickiness' in strategyConfig.parameters &&
|
||||
strategyConfig.parameters.stickiness === ''
|
||||
) {
|
||||
strategyConfig.parameters.stickiness = 'default';
|
||||
}
|
||||
|
||||
try {
|
||||
const newFeatureStrategy =
|
||||
await this.featureStrategiesStore.createStrategyFeatureEnv({
|
||||
|
@ -0,0 +1,28 @@
|
||||
'use strict';
|
||||
|
||||
exports.up = function (db, callback) {
|
||||
db.runSql(
|
||||
`
|
||||
INSERT INTO context_fields(name, description, sort_order, stickiness) VALUES('sessionId', 'Allows you to constrain on sessionId', 4, true);
|
||||
|
||||
UPDATE context_fields
|
||||
SET stickiness = true
|
||||
WHERE name LIKE 'userId' AND stickiness is null;
|
||||
`,
|
||||
callback,
|
||||
);
|
||||
};
|
||||
|
||||
exports.down = function (db, callback) {
|
||||
db.runSql(
|
||||
`
|
||||
DELETE FROM context_fields
|
||||
WHERE name LIKE 'sessionId';
|
||||
|
||||
UPDATE context_fields
|
||||
SET stickiness = null
|
||||
WHERE name LIKE 'userId';
|
||||
`,
|
||||
callback,
|
||||
);
|
||||
};
|
@ -17,8 +17,11 @@ let app: IUnleashTest;
|
||||
let db: ITestDb;
|
||||
|
||||
const defaultStrategy = {
|
||||
name: 'default',
|
||||
parameters: {},
|
||||
name: 'flexibleRollout',
|
||||
parameters: {
|
||||
rollout: '100',
|
||||
stickiness: '',
|
||||
},
|
||||
constraints: [],
|
||||
};
|
||||
|
||||
@ -844,3 +847,77 @@ test('Can add and remove tags at the same time', async () => {
|
||||
expect(res.body.tags).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
test('Should return "default" for stickiness when creating a flexibleRollout strategy with "" for stickiness', async () => {
|
||||
const username = 'toggle-feature';
|
||||
const feature = {
|
||||
name: 'test-featureA',
|
||||
description: 'the #1 feature',
|
||||
};
|
||||
const projectId = 'default';
|
||||
|
||||
await app.services.featureToggleServiceV2.createFeatureToggle(
|
||||
projectId,
|
||||
feature,
|
||||
username,
|
||||
);
|
||||
await app.services.featureToggleServiceV2.createStrategy(
|
||||
defaultStrategy,
|
||||
{ projectId, featureName: feature.name, environment: DEFAULT_ENV },
|
||||
username,
|
||||
);
|
||||
|
||||
await app.request
|
||||
.get(
|
||||
`/api/admin/projects/${projectId}/features/${feature.name}/environments/${DEFAULT_ENV}`,
|
||||
)
|
||||
.expect((res) => {
|
||||
const toggle = res.body;
|
||||
expect(toggle.strategies).toHaveLength(1);
|
||||
expect(toggle.strategies[0].parameters.stickiness).toBe('default');
|
||||
});
|
||||
|
||||
await app.request
|
||||
.get(`/api/admin/features/${feature.name}`)
|
||||
.expect((res) => {
|
||||
const toggle = res.body;
|
||||
expect(toggle.strategies).toHaveLength(1);
|
||||
expect(toggle.strategies[0].parameters.stickiness).toBe('default');
|
||||
});
|
||||
});
|
||||
|
||||
test('Should throw error when updating a flexibleRollout strategy with "" for stickiness', async () => {
|
||||
const username = 'toggle-feature';
|
||||
const feature = {
|
||||
name: 'test-featureB',
|
||||
description: 'the #1 feature',
|
||||
};
|
||||
const projectId = 'default';
|
||||
|
||||
await app.services.featureToggleServiceV2.createFeatureToggle(
|
||||
projectId,
|
||||
feature,
|
||||
username,
|
||||
);
|
||||
await app.services.featureToggleServiceV2.createStrategy(
|
||||
defaultStrategy,
|
||||
{ projectId, featureName: feature.name, environment: DEFAULT_ENV },
|
||||
username,
|
||||
);
|
||||
|
||||
const featureToggle =
|
||||
await app.services.featureToggleServiceV2.getFeatureToggle(
|
||||
feature.name,
|
||||
);
|
||||
|
||||
await app.request
|
||||
.patch(
|
||||
`/api/admin/projects/${projectId}/features/${feature.name}/environments/${DEFAULT_ENV}/strategies/${featureToggle.environments[0].strategies[0].id}`,
|
||||
)
|
||||
.send(defaultStrategy)
|
||||
.expect((res) => {
|
||||
const result = res.body;
|
||||
expect(res.status).toBe(400);
|
||||
expect(result.error).toBe('Request validation failed');
|
||||
});
|
||||
});
|
||||
|
@ -43,13 +43,15 @@ The UI shows the currently available projects. To create a new project, use the
|
||||
|
||||
The configuration of a new Project is now available. the following input is available to create the new Project.
|
||||
|
||||

|
||||

|
||||
|
||||
| Item | Description |
|
||||
| ------------ | ---------------------------------- |
|
||||
| Project Id | Id for this Project |
|
||||
| Project name | The name of the Project. |
|
||||
| Description | A short description of the project |
|
||||
| Item | Description |
|
||||
|--------------------|---------------------------------------------------------------------------------------------|
|
||||
| Project Id | Id for this Project |
|
||||
| Project name | The name of the Project. |
|
||||
| Description | A short description of the project |
|
||||
| Mode | The project [collaboration mode](/reference/project-collaboration-mode.md) |
|
||||
| Default Stickiness | The default stickiness for the project. This setting controls the default stickiness value for variants and for the gradual rollout strategy. |
|
||||
|
||||
## Deleting an existing project {#deleting-an-existing-project}
|
||||
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 126 KiB |
BIN
website/static/img/projects_save_new_project_v2.png
Normal file
BIN
website/static/img/projects_save_new_project_v2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 232 KiB |
Loading…
Reference in New Issue
Block a user