Navigation Menu

Skip to content

Commit

Permalink
chore: Patch 4.22.6 (#3603)
Browse files Browse the repository at this point in the history
<!-- 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>
  • Loading branch information
andreas-unleash committed Apr 25, 2023
1 parent 3698f1d commit 03d3ee7
Show file tree
Hide file tree
Showing 8 changed files with 160 additions and 30 deletions.
1 change: 0 additions & 1 deletion .github/workflows/e2e.frontend.yaml
Expand Up @@ -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
Expand Down
@@ -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;
Expand All @@ -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 (
Expand Down
18 changes: 18 additions & 0 deletions src/lib/services/feature-toggle-service.ts
Expand Up @@ -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(
Expand Down Expand Up @@ -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({
Expand Down
@@ -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,
);
};
81 changes: 79 additions & 2 deletions src/test/e2e/api/admin/feature.e2e.test.ts
Expand Up @@ -17,8 +17,11 @@ let app: IUnleashTest;
let db: ITestDb;

const defaultStrategy = {
name: 'default',
parameters: {},
name: 'flexibleRollout',
parameters: {
rollout: '100',
stickiness: '',
},
constraints: [],
};

Expand Down Expand Up @@ -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');
});
});
16 changes: 9 additions & 7 deletions website/docs/reference/projects.md
Expand Up @@ -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.

![A project creation form. The form fields are labeled "project ID", "name", and "description". The "Create" button is highlighted.](/img/projects_save_new_project.png)

| Item | Description |
| ------------ | ---------------------------------- |
| Project Id | Id for this Project |
| Project name | The name of the Project. |
| Description | A short description of the project |
![A project creation form. The "Create" button is highlighted.](/img/projects_save_new_project_v2.png)

| 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}

Expand Down
Binary file removed website/static/img/projects_save_new_project.png
Binary file not shown.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 03d3ee7

Please sign in to comment.