From 842ddd7855c3c888dbf1a36d8bee4b8cfee0f3c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gast=C3=B3n=20Fournier?= Date: Thu, 17 Apr 2025 17:32:21 +0200 Subject: [PATCH 1/5] WIP: removing the strategy for new installations of Unleash --- .../addons/feature-event-formatter-md.test.ts | 40 ------------------- src/lib/addons/feature-event-formatter-md.ts | 6 --- .../feature-evaluator/strategy/index.ts | 2 - .../strategy/user-with-id-strategy.ts | 17 -------- .../playground/offline-unleash-client.test.ts | 7 ---- .../spec/playground-feature-schema.test.ts | 8 ---- ...0421133845-add-sort-order-to-strategies.js | 1 - ...e-and-update-description-for-strategies.js | 1 - .../20230420125500-v5-strategy-changes.js | 4 -- src/migrations/default-strategies.json | 12 ------ src/test/arbitraries.test.ts | 8 ---- 11 files changed, 106 deletions(-) delete mode 100644 src/lib/features/playground/feature-evaluator/strategy/user-with-id-strategy.ts diff --git a/src/lib/addons/feature-event-formatter-md.test.ts b/src/lib/addons/feature-event-formatter-md.test.ts index 85f91dc32e01..77f8173d262a 100644 --- a/src/lib/addons/feature-event-formatter-md.test.ts +++ b/src/lib/addons/feature-event-formatter-md.test.ts @@ -352,46 +352,6 @@ const testCases: [string, IEvent][] = [ }, ], ), - [ - 'when userIds changed', - { - id: 920, - type: FEATURE_STRATEGY_UPDATE, - createdBy: 'user@company.com', - createdByUserId: SYSTEM_USER_ID, - createdAt: new Date('2022-06-01T10:03:11.549Z'), - data: { - name: 'userWithId', - constraints: [ - { - values: ['x', 'y'], - inverted: false, - operator: IN, - contextName: 'appName', - caseInsensitive: false, - }, - ], - parameters: { - userIds: 'a,b', - }, - sortOrder: 9999, - id: '9a995d94-5944-4897-a82f-0f7e65c2fb3f', - }, - preData: { - name: 'userWithId', - constraints: [], - parameters: { - userIds: '', - }, - sortOrder: 9999, - id: '9a995d94-5944-4897-a82f-0f7e65c2fb3f', - }, - tags: [], - featureName: 'new-feature', - project: 'my-other-project', - environment: 'production', - }, - ], [ 'when IPs changed', { diff --git a/src/lib/addons/feature-event-formatter-md.ts b/src/lib/addons/feature-event-formatter-md.ts index 120133215113..8c3537d11ff1 100644 --- a/src/lib/addons/feature-event-formatter-md.ts +++ b/src/lib/addons/feature-event-formatter-md.ts @@ -135,8 +135,6 @@ export class FeatureEventFormatterMd implements FeatureEventFormatter { return this.flexibleRolloutStrategyChangeText(event); case 'default': return this.defaultStrategyChangeText(event); - case 'userWithId': - return this.userWithIdStrategyChangeText(event); case 'remoteAddress': return this.remoteAddressStrategyChangeText(event); case 'applicationHostname': @@ -162,10 +160,6 @@ export class FeatureEventFormatterMd implements FeatureEventFormatter { return this.listOfValuesStrategyChangeText(event, 'IPs'); } - private userWithIdStrategyChangeText(event: IEvent) { - return this.listOfValuesStrategyChangeText(event, 'userIds'); - } - private listOfValuesStrategyChangeText( event: IEvent, propertyName: string, diff --git a/src/lib/features/playground/feature-evaluator/strategy/index.ts b/src/lib/features/playground/feature-evaluator/strategy/index.ts index bedd3771f0d4..3870413ee4c0 100644 --- a/src/lib/features/playground/feature-evaluator/strategy/index.ts +++ b/src/lib/features/playground/feature-evaluator/strategy/index.ts @@ -2,7 +2,6 @@ import DefaultStrategy from './default-strategy.js'; import GradualRolloutRandomStrategy from './gradual-rollout-random.js'; import GradualRolloutUserIdStrategy from './gradual-rollout-user-id.js'; import GradualRolloutSessionIdStrategy from './gradual-rollout-session-id.js'; -import UserWithIdStrategy from './user-with-id-strategy.js'; import RemoteAddressStrategy from './remote-address-strategy.js'; import FlexibleRolloutStrategy from './flexible-rollout-strategy.js'; import type { Strategy } from './strategy.js'; @@ -18,7 +17,6 @@ export const defaultStrategies: Array = [ new GradualRolloutRandomStrategy(), new GradualRolloutUserIdStrategy(), new GradualRolloutSessionIdStrategy(), - new UserWithIdStrategy(), new RemoteAddressStrategy(), new FlexibleRolloutStrategy(), new UnknownStrategy(), diff --git a/src/lib/features/playground/feature-evaluator/strategy/user-with-id-strategy.ts b/src/lib/features/playground/feature-evaluator/strategy/user-with-id-strategy.ts deleted file mode 100644 index 54eef4434200..000000000000 --- a/src/lib/features/playground/feature-evaluator/strategy/user-with-id-strategy.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Strategy } from './strategy.js'; -import type { Context } from '../context.js'; - -export default class UserWithIdStrategy extends Strategy { - constructor() { - super('userWithId'); - } - - isEnabled(parameters: { userIds?: string }, context: Context): boolean { - const userIdList = parameters.userIds - ? parameters.userIds.split(/\s*,\s*/) - : []; - return ( - context.userId !== undefined && userIdList.includes(context.userId) - ); - } -} diff --git a/src/lib/features/playground/offline-unleash-client.test.ts b/src/lib/features/playground/offline-unleash-client.test.ts index 66d596b2b11e..f8953a0cdaa5 100644 --- a/src/lib/features/playground/offline-unleash-client.test.ts +++ b/src/lib/features/playground/offline-unleash-client.test.ts @@ -432,13 +432,6 @@ describe('offline client', () => { stickiness: 'userId', }, }, - { - name: 'userWithId', - constraints: [], - parameters: { - userIds: 'uoea,ueoa', - }, - }, { name: 'remoteAddress', constraints: [], diff --git a/src/lib/openapi/spec/playground-feature-schema.test.ts b/src/lib/openapi/spec/playground-feature-schema.test.ts index cc620d267348..80ebd9468ae0 100644 --- a/src/lib/openapi/spec/playground-feature-schema.test.ts +++ b/src/lib/openapi/spec/playground-feature-schema.test.ts @@ -95,14 +95,6 @@ const playgroundStrategies = (): Arbitrary => }), ), - playgroundStrategy( - 'userWithId', - fc.record({ - userIds: fc - .uniqueArray(fc.emailAddress()) - .map((ids) => ids.join(',')), - }), - ), playgroundStrategy( 'remoteAddress', fc.record({ diff --git a/src/migrations/20210421133845-add-sort-order-to-strategies.js b/src/migrations/20210421133845-add-sort-order-to-strategies.js index cc5b3906153c..39ed78dd92d3 100644 --- a/src/migrations/20210421133845-add-sort-order-to-strategies.js +++ b/src/migrations/20210421133845-add-sort-order-to-strategies.js @@ -6,7 +6,6 @@ exports.up = function (db, cb) { ALTER TABLE strategies ADD COLUMN sort_order integer DEFAULT 9999; UPDATE strategies SET sort_order = 0 WHERE name = 'default'; UPDATE strategies SET sort_order = 1 WHERE name = 'flexibleRollout'; - UPDATE strategies SET sort_order = 2 WHERE name = 'userWithId'; UPDATE strategies SET sort_order = 3 WHERE name = 'remoteAddress'; UPDATE strategies SET sort_order = 4 WHERE name = 'applicationHostname'; `, diff --git a/src/migrations/20210421135405-add-display-name-and-update-description-for-strategies.js b/src/migrations/20210421135405-add-display-name-and-update-description-for-strategies.js index 1ddbfefcd697..323a9be2506d 100644 --- a/src/migrations/20210421135405-add-display-name-and-update-description-for-strategies.js +++ b/src/migrations/20210421135405-add-display-name-and-update-description-for-strategies.js @@ -6,7 +6,6 @@ exports.up = function (db, cb) { ALTER TABLE strategies ADD COLUMN display_name text; UPDATE strategies SET display_name = 'Standard', description = 'The standard strategy is strictly on / off for your entire userbase.' WHERE name = 'default'; UPDATE strategies SET display_name = 'Gradual rollout', description = 'Roll out to a percentage of your userbase, and ensure that the experience is the same for the user on each visit.' WHERE name = 'flexibleRollout'; - UPDATE strategies SET display_name = 'UserIDs', description = 'Enable the feature for a specific set of userIds.' WHERE name = 'userWithId'; UPDATE strategies SET display_name = 'IPs', description = 'Enable the feature for a specific set of IP addresses.' WHERE name = 'remoteAddress'; UPDATE strategies SET display_name = 'Hosts', description = 'Enable the feature for a specific set of hostnames.' WHERE name = 'applicationHostname'; `, diff --git a/src/migrations/20230420125500-v5-strategy-changes.js b/src/migrations/20230420125500-v5-strategy-changes.js index 9ed3282ed4a8..bdee9e4f61ac 100644 --- a/src/migrations/20230420125500-v5-strategy-changes.js +++ b/src/migrations/20230420125500-v5-strategy-changes.js @@ -9,13 +9,9 @@ exports.up = function (db, callback) { and deprecated and not exists (select * from feature_strategies where strategy_name = name limit 1); - -- deprecate strategies on v5 - update strategies set deprecated = true where name in ('userWithId'); - -- update strategy descriptions and sort order update strategies set sort_order = 1, description = 'This strategy turns on / off for your entire userbase. Prefer using "Gradual rollout" strategy (100%=on, 0%=off).' WHERE name = 'default'; update strategies set sort_order = 0 WHERE name = 'flexibleRollout'; - update strategies set description = 'Enable the feature for a specific set of userIds. Prefer using "Gradual rollout" strategy with user id constraints.' WHERE name = 'userWithId'; `, callback, ); diff --git a/src/migrations/default-strategies.json b/src/migrations/default-strategies.json index e430f150dbb1..3bda684b3b31 100644 --- a/src/migrations/default-strategies.json +++ b/src/migrations/default-strategies.json @@ -4,18 +4,6 @@ "description": "Default on/off strategy.", "parameters": [] }, - { - "name": "userWithId", - "description": "Active for users with a userId defined in the userIds-list", - "parameters": [ - { - "name": "userIds", - "type": "list", - "description": "", - "required": false - } - ] - }, { "name": "applicationHostname", "description": "Active for client instances with a hostName in the hostNames-list.", diff --git a/src/test/arbitraries.test.ts b/src/test/arbitraries.test.ts index 41066a6ec60c..15e12f514097 100644 --- a/src/test/arbitraries.test.ts +++ b/src/test/arbitraries.test.ts @@ -107,14 +107,6 @@ export const strategies = (): Arbitrary => }), ), - strategy( - 'userWithId', - fc.record({ - userIds: fc - .uniqueArray(fc.emailAddress()) - .map((ids) => ids.join(',')), - }), - ), strategy( 'remoteAddress', fc.record({ From 4d34d6de096563ecfc7c2ec879f591a136477f0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gast=C3=B3n=20Fournier?= Date: Thu, 17 Apr 2025 17:41:20 +0200 Subject: [PATCH 2/5] Small fixes --- .../__snapshots__/feature-event-formatter-md.test.ts.snap | 8 -------- src/test/e2e/api/client/feature.optimal304.e2e.test.ts | 4 ++-- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/src/lib/addons/__snapshots__/feature-event-formatter-md.test.ts.snap b/src/lib/addons/__snapshots__/feature-event-formatter-md.test.ts.snap index 6b6ace50ac05..6d01e082d0c8 100644 --- a/src/lib/addons/__snapshots__/feature-event-formatter-md.test.ts.snap +++ b/src/lib/addons/__snapshots__/feature-event-formatter-md.test.ts.snap @@ -239,11 +239,3 @@ exports[`Should format specialised text for events when strategy removed 1`] = ` "url": "unleashUrl/projects/my-other-project/features/new-feature", } `; - -exports[`Should format specialised text for events when userIds changed 1`] = ` -{ - "label": "Flag strategy updated", - "text": "*user@company.com* updated *[new-feature](unleashUrl/projects/my-other-project/features/new-feature)* in project *[my-other-project](unleashUrl/projects/my-other-project)* by updating strategy *userWithId* in *production* userIds from empty set of userIds to [a,b]; constraints from empty set of constraints to [appName is one of (x,y)]", - "url": "unleashUrl/projects/my-other-project/features/new-feature", -} -`; diff --git a/src/test/e2e/api/client/feature.optimal304.e2e.test.ts b/src/test/e2e/api/client/feature.optimal304.e2e.test.ts index f852106ff152..8ca7c5749a95 100644 --- a/src/test/e2e/api/client/feature.optimal304.e2e.test.ts +++ b/src/test/e2e/api/client/feature.optimal304.e2e.test.ts @@ -157,8 +157,8 @@ describe.each([ `"61824cd0:16:${etagVariant.name}"`, ); } else { - expect(res.headers.etag).toBe('"61824cd0:16"'); - expect(res.body.meta.etag).toBe('"61824cd0:16"'); + expect(res.headers.etag).toBe('"61824cd0:15"'); + expect(res.body.meta.etag).toBe('"61824cd0:15"'); } }); From e372e28858d630f1924c25dc619a9f7a3b8658b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gast=C3=B3n=20Fournier?= Date: Thu, 17 Apr 2025 17:48:44 +0200 Subject: [PATCH 3/5] Fix 304 test --- src/test/e2e/api/client/feature.optimal304.e2e.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/e2e/api/client/feature.optimal304.e2e.test.ts b/src/test/e2e/api/client/feature.optimal304.e2e.test.ts index 8ca7c5749a95..17605a03ea57 100644 --- a/src/test/e2e/api/client/feature.optimal304.e2e.test.ts +++ b/src/test/e2e/api/client/feature.optimal304.e2e.test.ts @@ -165,7 +165,7 @@ describe.each([ test(`returns ${etagVariant.feature_enabled ? 200 : 304} for pre-calculated hash${etagVariant.feature_enabled ? ' because hash changed' : ''}`, async () => { const res = await app.request .get('/api/client/features') - .set('if-none-match', '"61824cd0:16"') + .set('if-none-match', '"61824cd0:15"') .expect(etagVariant.feature_enabled ? 200 : 304); if (etagVariant.feature_enabled) { From 56c95a8086418f0f7aa47201df767ccbe2a82aa2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20G=C3=B3is?= Date: Tue, 3 Jun 2025 12:28:58 +0100 Subject: [PATCH 4/5] Remove user ids strategy 2 (#9806) Expands on the work done in https://github.com/Unleash/unleash/pull/9800 --- frontend/cypress/global.d.ts | 4 -- frontend/cypress/support/UI.ts | 51 ------------------- frontend/cypress/support/commands.ts | 5 -- .../ChangeRequestPermissions.test.tsx | 39 +++++++------- .../FeatureStrategyType.tsx | 10 ---- .../UserWithIdStrategy/UserWithId.tsx | 32 ------------ .../MilestoneStrategyType.tsx | 10 ---- frontend/src/utils/strategyNames.tsx | 5 -- .../client-feature-toggles.e2e.test.ts.snap | 4 +- src/lib/features/events/event-store.ts | 5 +- .../spec/client-application-schema.test.ts | 2 - .../api/client/feature.optimal304.e2e.test.ts | 16 +++--- .../api/legacy/unleash/admin/metrics.md | 1 - .../api/legacy/unleash/admin/strategies.md | 12 ----- .../reference/predefined-strategy-types.mdx | 4 ++ 15 files changed, 38 insertions(+), 162 deletions(-) delete mode 100644 frontend/src/component/feature/StrategyTypes/UserWithIdStrategy/UserWithId.tsx diff --git a/frontend/cypress/global.d.ts b/frontend/cypress/global.d.ts index e3162a2cca4b..eaec270d882e 100644 --- a/frontend/cypress/global.d.ts +++ b/frontend/cypress/global.d.ts @@ -54,10 +54,6 @@ declare namespace Cypress { deleteSegment_UI(segmentName: string, id: string): Chainable; // STRATEGY - addUserIdStrategyToFeature_UI( - featureName: string, - projectName: string, - ): Chainable; addFlexibleRolloutStrategyToFeature_UI( options: AddFlexibleRolloutStrategyOptions, ): Chainable; diff --git a/frontend/cypress/support/UI.ts b/frontend/cypress/support/UI.ts index 66b11ce76cfd..61cb1ff2241c 100644 --- a/frontend/cypress/support/UI.ts +++ b/frontend/cypress/support/UI.ts @@ -257,57 +257,6 @@ export const deleteFeatureStrategy_UI = ( return cy.wait('@deleteUserStrategy'); }; -export const addUserIdStrategyToFeature_UI = ( - featureToggleName: string, - projectName: string, -): Chainable => { - const project = projectName || 'default'; - cy.visit( - `/projects/${project}/features/${featureToggleName}/strategies/create?environmentId=development&strategyName=userWithId`, - ); - - if (ENTERPRISE) { - cy.get('[data-testid=ADD_CONSTRAINT_ID]').click(); - cy.get('[data-testid=CONSTRAINT_AUTOCOMPLETE_ID]') - .type('{downArrow}'.repeat(1)) - .type('{enter}'); - cy.get('[data-testid=DIALOGUE_CONFIRM_ID]').click(); - } - - cy.get('[data-testid=STRATEGY_INPUT_LIST]') - .type('user1') - .type('{enter}') - .type('user2') - .type('{enter}'); - cy.get('[data-testid=ADD_TO_STRATEGY_INPUT_LIST]').click(); - - cy.intercept( - 'POST', - `/api/admin/projects/default/features/${featureToggleName}/environments/*/strategies`, - (req) => { - expect(req.body.name).to.equal('userWithId'); - - expect(req.body.parameters.userIds.length).to.equal(11); - - if (ENTERPRISE) { - expect(req.body.constraints.length).to.equal(1); - } else { - expect(req.body.constraints.length).to.equal(0); - } - - req.continue((res) => { - strategyId = res.body.id; - }); - }, - ).as('addStrategyToFeature'); - - // this one needs to wait until the dropdown selector of stickiness is set, that's why waitForAnimations: true - cy.get(`[data-testid=STRATEGY_FORM_SUBMIT_ID]`) - .first() - .click({ waitForAnimations: true }); - return cy.wait('@addStrategyToFeature'); -}; - export const logout_UI = (): Chainable => { return cy.visit('/logout'); }; diff --git a/frontend/cypress/support/commands.ts b/frontend/cypress/support/commands.ts index 867ec13a7bfa..d664b63e59b9 100644 --- a/frontend/cypress/support/commands.ts +++ b/frontend/cypress/support/commands.ts @@ -10,7 +10,6 @@ import { deleteSegment_UI, deleteFeatureStrategy_UI, addFlexibleRolloutStrategyToFeature_UI, - addUserIdStrategyToFeature_UI, updateFlexibleRolloutStrategy_UI, do_login, } from './UI.ts'; @@ -45,10 +44,6 @@ Cypress.Commands.add('updateUserPassword_API', updateUserPassword_API); Cypress.Commands.add('createFeature_UI', createFeature_UI); Cypress.Commands.add('deleteFeatureStrategy_UI', deleteFeatureStrategy_UI); Cypress.Commands.add('createFeature_API', createFeature_API); -Cypress.Commands.add( - 'addUserIdStrategyToFeature_UI', - addUserIdStrategyToFeature_UI, -); Cypress.Commands.add( 'addFlexibleRolloutStrategyToFeature_UI', addFlexibleRolloutStrategyToFeature_UI, diff --git a/frontend/src/component/changeRequest/ChangeRequestPermissions.test.tsx b/frontend/src/component/changeRequest/ChangeRequestPermissions.test.tsx index b7d8fa70ecb3..4cbeed3c2e5e 100644 --- a/frontend/src/component/changeRequest/ChangeRequestPermissions.test.tsx +++ b/frontend/src/component/changeRequest/ChangeRequestPermissions.test.tsx @@ -98,17 +98,20 @@ const setupOtherRoutes = (feature: string) => { deprecated: false, }, { - displayName: 'UserIDs', - name: 'userWithId', + displayName: 'Gradual rollout', + name: 'flexibleRollout', editable: false, description: - 'Enable the feature for a specific set of userIds.', + 'The gradual rollout strategy allows you to gradually roll out a feature to a percentage of users.', parameters: [ { - name: 'userIds', - type: 'list', - description: '', - required: false, + name: 'rollout', + }, + { + name: 'stickiness', + }, + { + name: 'groupId', }, ], deprecated: false, @@ -214,12 +217,10 @@ const UnleashUiSetup: FC<{ ); -const strategiesAreDisplayed = async ( - firstStrategy: string, - secondStrategy: string, -) => { - await screen.findByText(firstStrategy); - await screen.findByText(secondStrategy); +const strategiesAreDisplayed = async (strategies: string[]) => { + for (const strategy of strategies) { + await screen.findByText(strategy); + } }; const getDeleteButtons = async () => { @@ -299,7 +300,7 @@ test('open mode + non-project member can perform basic change request actions', const featureName = 'test'; featureEnvironments(featureName, [ { name: 'development', strategies: [] }, - { name: 'production', strategies: ['userWithId'] }, + { name: 'production', strategies: ['flexibleRollout'] }, { name: 'custom', strategies: ['default'] }, ]); userIsMemberOfProjects([]); @@ -318,7 +319,7 @@ test('open mode + non-project member can perform basic change request actions', ); await openEnvironments(['development', 'production', 'custom']); - await strategiesAreDisplayed('UserIDs', 'Standard'); + await strategiesAreDisplayed(['Gradual rollout', 'Standard']); await deleteButtonsActiveInChangeRequestEnv(); await copyButtonsActiveInOtherEnv(); }); @@ -328,7 +329,7 @@ test('protected mode + project member can perform basic change request actions', const featureName = 'test'; featureEnvironments(featureName, [ { name: 'development', strategies: [] }, - { name: 'production', strategies: ['userWithId'] }, + { name: 'production', strategies: ['flexibleRollout'] }, { name: 'custom', strategies: ['default'] }, ]); userIsMemberOfProjects([project]); @@ -348,7 +349,7 @@ test('protected mode + project member can perform basic change request actions', await openEnvironments(['development', 'production', 'custom']); - await strategiesAreDisplayed('UserIDs', 'Standard'); + await strategiesAreDisplayed(['Gradual rollout', 'Standard']); await deleteButtonsActiveInChangeRequestEnv(); await copyButtonsActiveInOtherEnv(); }); @@ -358,7 +359,7 @@ test.skip('protected mode + non-project member cannot perform basic change reque const featureName = 'test'; featureEnvironments(featureName, [ { name: 'development', strategies: [] }, - { name: 'production', strategies: ['userWithId'] }, + { name: 'production', strategies: ['flexibleRollout'] }, { name: 'custom', strategies: ['default'] }, ]); userIsMemberOfProjects([]); @@ -378,7 +379,7 @@ test.skip('protected mode + non-project member cannot perform basic change reque await openEnvironments(['development', 'production', 'custom']); - await strategiesAreDisplayed('UserIDs', 'Standard'); + await strategiesAreDisplayed(['Gradual rollout', 'Standard']); await deleteButtonsInactiveInChangeRequestEnv(); await copyButtonsActiveInOtherEnv(); }); diff --git a/frontend/src/component/feature/FeatureStrategy/FeatureStrategyType/FeatureStrategyType.tsx b/frontend/src/component/feature/FeatureStrategy/FeatureStrategyType/FeatureStrategyType.tsx index 89c18dfc1897..cadc095128bd 100644 --- a/frontend/src/component/feature/FeatureStrategy/FeatureStrategyType/FeatureStrategyType.tsx +++ b/frontend/src/component/feature/FeatureStrategy/FeatureStrategyType/FeatureStrategyType.tsx @@ -1,7 +1,6 @@ import type { IFeatureStrategy, IStrategy } from 'interfaces/strategy'; import DefaultStrategy from 'component/feature/StrategyTypes/DefaultStrategy/DefaultStrategy'; import FlexibleStrategy from 'component/feature/StrategyTypes/FlexibleStrategy/FlexibleStrategy'; -import UserWithIdStrategy from 'component/feature/StrategyTypes/UserWithIdStrategy/UserWithId'; import GeneralStrategy from 'component/feature/StrategyTypes/GeneralStrategy/GeneralStrategy'; import useUnleashContext from 'hooks/api/getters/useUnleashContext/useUnleashContext'; import produce from 'immer'; @@ -52,15 +51,6 @@ export const FeatureStrategyType = ({ errors={errors} /> ); - case 'userWithId': - return ( - - ); default: return ( void; - editable: boolean; - errors: IFormErrors; -} - -const UserWithIdStrategy = ({ - editable, - parameters, - updateParameter, - errors, -}: IUserWithIdStrategyProps) => { - return ( -
- -
- ); -}; - -export default UserWithIdStrategy; diff --git a/frontend/src/component/releases/ReleasePlanTemplate/TemplateForm/MilestoneStrategy/MilestoneStrategyType.tsx b/frontend/src/component/releases/ReleasePlanTemplate/TemplateForm/MilestoneStrategy/MilestoneStrategyType.tsx index d06cadc4c034..1a0582c7ccd6 100644 --- a/frontend/src/component/releases/ReleasePlanTemplate/TemplateForm/MilestoneStrategy/MilestoneStrategyType.tsx +++ b/frontend/src/component/releases/ReleasePlanTemplate/TemplateForm/MilestoneStrategy/MilestoneStrategyType.tsx @@ -3,7 +3,6 @@ import type { IReleasePlanMilestoneStrategy } from 'interfaces/releasePlans'; import type { IStrategy } from 'interfaces/strategy'; import { MilestoneStrategyTypeFlexible } from './MilestoneStrategyTypeFlexible.tsx'; import GeneralStrategy from 'component/feature/StrategyTypes/GeneralStrategy/GeneralStrategy'; -import UserWithIdStrategy from 'component/feature/StrategyTypes/UserWithIdStrategy/UserWithId'; import DefaultStrategy from 'component/feature/StrategyTypes/DefaultStrategy/DefaultStrategy'; interface IMilestoneStrategyTypeProps { @@ -36,15 +35,6 @@ export const MilestoneStrategyType = ({ editable={true} /> ); - case 'userWithId': - return ( - - ); default: return ( { return LanguageIcon; case 'flexibleRollout': return RolloutSvgIcon; - case 'userWithId': - return PeopleIcon; case 'applicationHostname': return LocationOnIcon; case 'releasePlanTemplate': @@ -47,7 +44,6 @@ export const BuiltInStrategies = [ 'gradualRolloutSessionId', 'gradualRolloutUserId', 'remoteAddress', - 'userWithId', ]; export const GetFeatureStrategyIcon: FC<{ strategyName: string }> = ({ @@ -66,5 +62,4 @@ export const formattedStrategyNames: Record = { gradualRolloutSessionId: 'Sessions', gradualRolloutUserId: 'Users', remoteAddress: 'IPs', - userWithId: 'UserIDs', }; diff --git a/src/lib/features/client-feature-toggles/tests/__snapshots__/client-feature-toggles.e2e.test.ts.snap b/src/lib/features/client-feature-toggles/tests/__snapshots__/client-feature-toggles.e2e.test.ts.snap index 34d8d7b45153..c30a0ec6bc63 100644 --- a/src/lib/features/client-feature-toggles/tests/__snapshots__/client-feature-toggles.e2e.test.ts.snap +++ b/src/lib/features/client-feature-toggles/tests/__snapshots__/client-feature-toggles.e2e.test.ts.snap @@ -63,9 +63,9 @@ exports[`should match snapshot from /api/client/features 1`] = ` }, ], "meta": { - "etag": ""61824cd0:20"", + "etag": ""61824cd0:19"", "queryHash": "61824cd0", - "revisionId": 20, + "revisionId": 19, }, "query": { "environment": "default", diff --git a/src/lib/features/events/event-store.ts b/src/lib/features/events/event-store.ts index 8bcddc0e8b39..d06fe038bcd4 100644 --- a/src/lib/features/events/event-store.ts +++ b/src/lib/features/events/event-store.ts @@ -354,7 +354,10 @@ class EventStore implements IEventStore { .select(EVENT_COLUMNS) .from(TABLE) .limit(100) - .orderBy('created_at', 'desc'); + .orderBy([ + { column: 'created_at', order: 'desc' }, + { column: 'id', order: 'desc' }, + ]); if (query) { qB = qB.where(query); } diff --git a/src/lib/openapi/spec/client-application-schema.test.ts b/src/lib/openapi/spec/client-application-schema.test.ts index 527eeea5d3f6..e025405a61b5 100644 --- a/src/lib/openapi/spec/client-application-schema.test.ts +++ b/src/lib/openapi/spec/client-application-schema.test.ts @@ -48,7 +48,6 @@ test('clientApplicationSchema go-sdk request', () => { "gradualRolloutSessionId", "gradualRolloutUserId", "remoteAddress", - "userWithId", "flexibleRollout" ], "started": "2022-06-24T09:59:12.822607943+02:00", @@ -75,7 +74,6 @@ test('clientApplicationSchema node-sdk request', () => { "gradualRolloutRandom", "gradualRolloutUserId", "gradualRolloutSessionId", - "userWithId", "remoteAddress", "flexibleRollout" ], diff --git a/src/test/e2e/api/client/feature.optimal304.e2e.test.ts b/src/test/e2e/api/client/feature.optimal304.e2e.test.ts index 17605a03ea57..a4886882ca0d 100644 --- a/src/test/e2e/api/client/feature.optimal304.e2e.test.ts +++ b/src/test/e2e/api/client/feature.optimal304.e2e.test.ts @@ -152,9 +152,9 @@ describe.each([ .expect(200); if (etagVariant.feature_enabled) { - expect(res.headers.etag).toBe(`"61824cd0:16:${etagVariant.name}"`); + expect(res.headers.etag).toBe(`"61824cd0:15:${etagVariant.name}"`); expect(res.body.meta.etag).toBe( - `"61824cd0:16:${etagVariant.name}"`, + `"61824cd0:15:${etagVariant.name}"`, ); } else { expect(res.headers.etag).toBe('"61824cd0:15"'); @@ -169,9 +169,9 @@ describe.each([ .expect(etagVariant.feature_enabled ? 200 : 304); if (etagVariant.feature_enabled) { - expect(res.headers.etag).toBe(`"61824cd0:16:${etagVariant.name}"`); + expect(res.headers.etag).toBe(`"61824cd0:15:${etagVariant.name}"`); expect(res.body.meta.etag).toBe( - `"61824cd0:16:${etagVariant.name}"`, + `"61824cd0:15:${etagVariant.name}"`, ); } }); @@ -193,13 +193,13 @@ describe.each([ .expect(200); if (etagVariant.feature_enabled) { - expect(res.headers.etag).toBe(`"61824cd0:16:${etagVariant.name}"`); + expect(res.headers.etag).toBe(`"61824cd0:15:${etagVariant.name}"`); expect(res.body.meta.etag).toBe( - `"61824cd0:16:${etagVariant.name}"`, + `"61824cd0:15:${etagVariant.name}"`, ); } else { - expect(res.headers.etag).toBe('"61824cd0:16"'); - expect(res.body.meta.etag).toBe('"61824cd0:16"'); + expect(res.headers.etag).toBe('"61824cd0:15"'); + expect(res.body.meta.etag).toBe('"61824cd0:15"'); } }); }); diff --git a/website/docs/reference/api/legacy/unleash/admin/metrics.md b/website/docs/reference/api/legacy/unleash/admin/metrics.md index 35d3835e3f40..b245e1747b79 100644 --- a/website/docs/reference/api/legacy/unleash/admin/metrics.md +++ b/website/docs/reference/api/legacy/unleash/admin/metrics.md @@ -180,7 +180,6 @@ This endpoint gives insight into details about application seen per feature flag "abTest", "default", "betaUser", - "userWithId", "byHostName", "gradualRolloutWithSessionId", "gradualRollout", diff --git a/website/docs/reference/api/legacy/unleash/admin/strategies.md b/website/docs/reference/api/legacy/unleash/admin/strategies.md index 197aca6c404a..70aec153d490 100644 --- a/website/docs/reference/api/legacy/unleash/admin/strategies.md +++ b/website/docs/reference/api/legacy/unleash/admin/strategies.md @@ -21,18 +21,6 @@ Used to fetch all defined strategies and their defined parameters. "description": "Default on/off strategy.", "parameters": [] }, - { - "name": "userWithId", - "description": "Active for userId specified in the comma seperated 'userIds' parameter.", - "parameters": [ - { - "name": "userIds", - "type": "list", - "description": "List of unique userIds the feature should be active for.", - "required": true - } - ] - }, { "name": "gradualRollout", "description": "Gradual rollout to logged in users", diff --git a/website/docs/reference/predefined-strategy-types.mdx b/website/docs/reference/predefined-strategy-types.mdx index 57ea3ee3e81d..1214d70e240a 100644 --- a/website/docs/reference/predefined-strategy-types.mdx +++ b/website/docs/reference/predefined-strategy-types.mdx @@ -8,6 +8,10 @@ Predefined strategy types are a legacy implementation. Please use the [default s ## UserIDs +:::warning +The `userWithId` strategy was removed in Unleash v7.0.0 for new installations. Instead use a gradual rollout strategy with a user ID stickiness and constraints. +::: + The `userWithId` strategy is active for users with a `userId` defined in the `userIds` list. **Parameters:** From 96944583e494874fefe29bb7eaa19955e15e0134 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gast=C3=B3n=20Fournier?= Date: Wed, 4 Jun 2025 09:22:45 +0200 Subject: [PATCH 5/5] fix tests after merge --- .../api/client/feature.optimal304.e2e.test.ts | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/test/e2e/api/client/feature.optimal304.e2e.test.ts b/src/test/e2e/api/client/feature.optimal304.e2e.test.ts index a4886882ca0d..f852106ff152 100644 --- a/src/test/e2e/api/client/feature.optimal304.e2e.test.ts +++ b/src/test/e2e/api/client/feature.optimal304.e2e.test.ts @@ -152,26 +152,26 @@ describe.each([ .expect(200); if (etagVariant.feature_enabled) { - expect(res.headers.etag).toBe(`"61824cd0:15:${etagVariant.name}"`); + expect(res.headers.etag).toBe(`"61824cd0:16:${etagVariant.name}"`); expect(res.body.meta.etag).toBe( - `"61824cd0:15:${etagVariant.name}"`, + `"61824cd0:16:${etagVariant.name}"`, ); } else { - expect(res.headers.etag).toBe('"61824cd0:15"'); - expect(res.body.meta.etag).toBe('"61824cd0:15"'); + expect(res.headers.etag).toBe('"61824cd0:16"'); + expect(res.body.meta.etag).toBe('"61824cd0:16"'); } }); test(`returns ${etagVariant.feature_enabled ? 200 : 304} for pre-calculated hash${etagVariant.feature_enabled ? ' because hash changed' : ''}`, async () => { const res = await app.request .get('/api/client/features') - .set('if-none-match', '"61824cd0:15"') + .set('if-none-match', '"61824cd0:16"') .expect(etagVariant.feature_enabled ? 200 : 304); if (etagVariant.feature_enabled) { - expect(res.headers.etag).toBe(`"61824cd0:15:${etagVariant.name}"`); + expect(res.headers.etag).toBe(`"61824cd0:16:${etagVariant.name}"`); expect(res.body.meta.etag).toBe( - `"61824cd0:15:${etagVariant.name}"`, + `"61824cd0:16:${etagVariant.name}"`, ); } }); @@ -193,13 +193,13 @@ describe.each([ .expect(200); if (etagVariant.feature_enabled) { - expect(res.headers.etag).toBe(`"61824cd0:15:${etagVariant.name}"`); + expect(res.headers.etag).toBe(`"61824cd0:16:${etagVariant.name}"`); expect(res.body.meta.etag).toBe( - `"61824cd0:15:${etagVariant.name}"`, + `"61824cd0:16:${etagVariant.name}"`, ); } else { - expect(res.headers.etag).toBe('"61824cd0:15"'); - expect(res.body.meta.etag).toBe('"61824cd0:15"'); + expect(res.headers.etag).toBe('"61824cd0:16"'); + expect(res.body.meta.etag).toBe('"61824cd0:16"'); } }); });