Skip to content

feat: label variant key UI#7745

Open
Zaimwa9 wants to merge 6 commits into
mainfrom
feat/label-variant-key-ui
Open

feat: label variant key UI#7745
Zaimwa9 wants to merge 6 commits into
mainfrom
feat/label-variant-key-ui

Conversation

@Zaimwa9

@Zaimwa9 Zaimwa9 commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Thanks for submitting a PR! Please check the boxes below:

  • I have read the Contributing Guide.
  • I have added information to docs/ if required so people know about the feature.
  • I have filled in the "Changes" section below.
  • I have filled in the "How did you test this code" section below.

Changes

Adds UI to set a label on multivariate variants (maps to the backend key field) and revamps the variants section of the feature modal's Value tab.

Variant labels

  • Each variant shows an inline-editable label (slug characters only, max 255, control reserved, unique per feature).
  • New variants get a default label (Variant_n, first free number) that is persisted on save; existing variants with no key fall back to a display-only placeholder.
  • No save-flow changes — the key persists through the existing writable /mv-options/ endpoint.

Improvements

  • Added error management at the MV option level (returning invalid key or duplicated one)
  • Error message appears in card
  • Success response from MV options update only are also showing a success toast and updating the edition colorswatch

Variants UI revamp

  • Each variant renders in a bordered, shadowed card: label header with a "Not saved" chip + delete icon.
  • "Variants" section header with a primary "+ Add variant" button (outline "Create A/B/n Test" when none exist), and a tooltip explaining labels are required for experiments (behind experimental_flags).
  • Control Value title shows the control percentage as a chip.
  • Input gains reusable underline and centered props; aligned the value editor height with adjacent inputs.

Experiment setup page

  • The variations table shows the variant label/key as the name and drops the unused Description column; ContentCard gains a description prop used by the setup step.

How did you test this code?

  • Extended the E2E flag test: editVariantLabel edits a variant's label via the new UI, saves, reopens the modal, and asserts persistence. Audited existing selectors (add-variation, featureVariationValue*, featureVariationWeight*, delete-multivariate) for compatibility with the new layout.
  • Added Storybook stories for the new Input variants (Underline, UnderlineCentered).
  • Manually, against a local API:
    1. Create a multivariate feature; add variants — they default to Variant_1, Variant_2
    2. Rename a label; verify validation (spaces, control, duplicates rejected with inline errors) and the "Not saved" chip appearing only on edited variants.
    3. Save, reopen — labels persist; clear a label — it saves as null and the placeholder returns.
image image

@Zaimwa9 Zaimwa9 requested a review from a team as a code owner June 10, 2026 10:09
@Zaimwa9 Zaimwa9 requested review from talissoncosta and removed request for a team June 10, 2026 10:09
@vercel

vercel Bot commented Jun 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
flagsmith-frontend-preview Ready Ready Preview, Comment Jun 10, 2026 3:57pm
flagsmith-frontend-staging Ready Ready Preview, Comment Jun 10, 2026 3:57pm
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Ignored Ignored Preview Jun 10, 2026 3:57pm

Request Review

@Zaimwa9 Zaimwa9 changed the title Feat/label variant key UI feat: label variant key UI Jun 10, 2026
@github-actions github-actions Bot added the front-end Issue related to the React Front End Dashboard label Jun 10, 2026
@github-actions

github-actions Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Docker builds report

Image Build Status Security report
ghcr.io/flagsmith/flagsmith-api-test:pr-7745 Finished ✅ Skipped
ghcr.io/flagsmith/flagsmith-e2e:pr-7745 Finished ✅ Skipped
ghcr.io/flagsmith/flagsmith:pr-7745 Finished ✅ Results
ghcr.io/flagsmith/flagsmith-private-cloud:pr-7745 Finished ✅ Results

@github-actions

github-actions Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Playwright Test Results (oss - depot-ubuntu-latest-16)

passed  1 passed

Details

stats  1 test across 1 suite
duration  40.6 seconds
commit  63dab47
info  🔄 Run: #17381 (attempt 1)

Playwright Test Results (oss - depot-ubuntu-latest-arm-16)

failed  1 failed

Details

stats  1 test across 1 suite
duration  22 seconds
commit  63dab47
info  📦 Artifacts: View test results and HTML report
🔄 Run: #17381 (attempt 1)

Failed tests

firefox › tests/segment-test.pw.ts › Segment test 1 - Create, update, and manage segments with multivariate flags @oss

### Playwright Test Results (private-cloud - depot-ubuntu-latest-16)

passed  2 passed

Details

stats  2 tests across 2 suites
duration  31.5 seconds
commit  63dab47
info  🔄 Run: #17381 (attempt 1)

Playwright Test Results (private-cloud - depot-ubuntu-latest-arm-16)

passed  3 passed

Details

stats  3 tests across 3 suites
duration  41.7 seconds
commit  63dab47
info  🔄 Run: #17381 (attempt 1)

Playwright Test Results (oss - depot-ubuntu-latest-arm-16)

passed  1 passed

Details

stats  1 test across 1 suite
duration  44.8 seconds
commit  63dab47
info  🔄 Run: #17381 (attempt 2)

Playwright Test Results (oss - depot-ubuntu-latest-arm-16)

passed  1 passed

Details

stats  1 test across 1 suite
duration  38 seconds
commit  385dfbe
info  🔄 Run: #17394 (attempt 1)

Playwright Test Results (oss - depot-ubuntu-latest-16)

passed  1 passed

Details

stats  1 test across 1 suite
duration  41.8 seconds
commit  385dfbe
info  🔄 Run: #17394 (attempt 1)

Playwright Test Results (private-cloud - depot-ubuntu-latest-arm-16)

passed  3 passed

Details

stats  3 tests across 3 suites
duration  44 seconds
commit  385dfbe
info  🔄 Run: #17394 (attempt 1)

Playwright Test Results (private-cloud - depot-ubuntu-latest-16)

passed  2 passed

Details

stats  2 tests across 2 suites
duration  39.1 seconds
commit  385dfbe
info  🔄 Run: #17394 (attempt 1)

Playwright Test Results (oss - depot-ubuntu-latest-16)

passed  1 passed

Details

stats  1 test across 1 suite
duration  37 seconds
commit  b50a56d
info  🔄 Run: #17395 (attempt 1)

Playwright Test Results (oss - depot-ubuntu-latest-arm-16)

passed  1 passed

Details

stats  1 test across 1 suite
duration  43.6 seconds
commit  b50a56d
info  🔄 Run: #17395 (attempt 1)

Playwright Test Results (private-cloud - depot-ubuntu-latest-arm-16)

passed  1 passed

Details

stats  1 test across 1 suite
duration  42.5 seconds
commit  b50a56d
info  🔄 Run: #17395 (attempt 1)

Playwright Test Results (private-cloud - depot-ubuntu-latest-16)

passed  1 passed

Details

stats  1 test across 1 suite
duration  46.1 seconds
commit  b50a56d
info  🔄 Run: #17395 (attempt 1)

Playwright Test Results (oss - depot-ubuntu-latest-16)

passed  1 passed

Details

stats  1 test across 1 suite
duration  39.7 seconds
commit  b50a56d
info  🔄 Run: #17396 (attempt 1)

Playwright Test Results (oss - depot-ubuntu-latest-arm-16)

passed  1 passed

Details

stats  1 test across 1 suite
duration  34.5 seconds
commit  b50a56d
info  🔄 Run: #17396 (attempt 1)

Playwright Test Results (private-cloud - depot-ubuntu-latest-16)

failed  3 failed

Details

stats  3 tests across 3 suites
duration  22.7 seconds
commit  b50a56d
info  📦 Artifacts: View test results and HTML report
🔄 Run: #17396 (attempt 1)

Failed tests

firefox › tests/change-request-test.pw.ts › Change Request Tests › Change requests can be created, approved, and published with 4-eyes approval @enterprise
firefox › tests/roles-test.pw.ts › Roles Tests › Roles can be created with project and environment permissions @enterprise
firefox › tests/sso-test.pw.ts › SCIM Tests › SCIM configuration can be created, regenerated, and deleted @enterprise

### Playwright Test Results (oss - depot-ubuntu-latest-16)

passed  1 passed

Details

stats  1 test across 1 suite
duration  36.9 seconds
commit  b50a56d
info  🔄 Run: #17396 (attempt 2)

Playwright Test Results (oss - depot-ubuntu-latest-arm-16)

passed  1 passed

Details

stats  1 test across 1 suite
duration  43.6 seconds
commit  b50a56d
info  🔄 Run: #17396 (attempt 3)

Playwright Test Results (oss - depot-ubuntu-latest-16)

passed  2 passed

Details

stats  2 tests across 2 suites
duration  31.9 seconds
commit  b50a56d
info  🔄 Run: #17396 (attempt 3)

Playwright Test Results (private-cloud - depot-ubuntu-latest-arm-16)

passed  2 passed

Details

stats  2 tests across 2 suites
duration  58.3 seconds
commit  b50a56d
info  🔄 Run: #17396 (attempt 3)

Playwright Test Results (oss - depot-ubuntu-latest-16)

passed  1 passed

Details

stats  1 test across 1 suite
duration  40.4 seconds
commit  5df610c
info  🔄 Run: #17398 (attempt 1)

Playwright Test Results (private-cloud - depot-ubuntu-latest-arm-16)

failed  3 failed

Details

stats  3 tests across 3 suites
duration  23.1 seconds
commit  5df610c
info  📦 Artifacts: View test results and HTML report
🔄 Run: #17398 (attempt 1)

Failed tests

firefox › tests/change-request-test.pw.ts › Change Request Tests › Change requests can be created, approved, and published with 4-eyes approval @enterprise
firefox › tests/roles-test.pw.ts › Roles Tests › Roles can be created with project and environment permissions @enterprise
firefox › tests/sso-test.pw.ts › SCIM Tests › SCIM configuration can be created, regenerated, and deleted @enterprise

### Playwright Test Results (oss - depot-ubuntu-latest-arm-16)

passed  1 passed

Details

stats  1 test across 1 suite
duration  38.2 seconds
commit  1adfffb
info  🔄 Run: #17399 (attempt 1)

@github-actions

github-actions Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Visual Regression

15 screenshots compared. See report for details.
View full report

@Zaimwa9

Zaimwa9 commented Jun 10, 2026

Copy link
Copy Markdown
Contributor Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces the ability to define and edit stable, human-readable keys (labels) for multivariate feature variations in the UI. It refactors the feature modal's variation tab to display variants as cards, adds inline editing for variant labels, and tracks unsaved changes with a 'Not saved' badge. Additionally, it updates the Input and ContentCard components, adds corresponding Storybook stories, and includes Playwright E2E tests. The review feedback highlights three key improvements: ensuring unique default variant names by accounting for fallback display names, normalizing null and undefined values to prevent false positives on the 'Not saved' badge, and falling back to an empty string for cleared weight inputs to avoid React controlled-to-uncontrolled warnings.

Comment thread frontend/web/components/modals/create-feature/tabs/FeatureValueTab.tsx Outdated
@Zaimwa9 Zaimwa9 marked this pull request as ready for review June 10, 2026 15:11
@Zaimwa9

Zaimwa9 commented Jun 10, 2026

Copy link
Copy Markdown
Contributor Author

/gemini review

@Zaimwa9

Zaimwa9 commented Jun 10, 2026

Copy link
Copy Markdown
Contributor Author

@gemini can you please have a thorough look of the data flow from the creation of a multivariate option, it's initial creation (POST) request and a direct update (PUT) without any full refresh

@gemini-code-assist

Copy link
Copy Markdown
Contributor

The data flow for multivariate options in this PR follows a batch-save pattern, where changes are managed in the local state of the CreateFeatureModal and persisted only when the user saves the modal. Here is the breakdown of that flow:

1. State Initialization

  • The CreateFeatureModal initializes the projectFlag state, which contains the multivariate_options array.
  • The originalMultivariateOptions memoized value is used to track the initial state, allowing the UI to identify which variants have unsaved changes (unsavedVariations).

2. Local Updates

  • When a user edits a variant label via VariationKeyLabel, the onChange callback updates the key property within the local multivariate_options array in the CreateFeatureModal state via onProjectFlagChange.
  • Because this is a local state update, there is no immediate API call, which prevents unnecessary network traffic while the user is still editing.

3. Change Tracking

  • The unsavedVariations array is computed by comparing the current multivariate_options with originalMultivariateOptions. This allows the UI to display the "Not saved" chip immediately upon modification.

4. Persistence (Save Flow)

  • When the user clicks the save button, saveFeatureValue is triggered. This function sends the entire updated multivariate_options array to the backend.
  • The backend then processes this payload. If you are observing a direct update (PUT) without a full refresh, it is because the frontend is patching the local state and then performing a single API request to synchronize that state with the server.

Considerations for "Direct Updates"

If your requirement is to perform a direct update (immediate PUT) on every change without waiting for the modal save, you would need to:

  1. Move the API call logic into the onChange handler of the VariationKeyLabel component.
  2. Ensure the backend endpoint is optimized to handle partial updates for specific variant keys to avoid race conditions if multiple fields are updated rapidly.

Currently, the batch-save approach is safer for maintaining consistency across the multivariate options, especially since the key must be unique per feature.

@Zaimwa9

Zaimwa9 commented Jun 10, 2026

Copy link
Copy Markdown
Contributor Author

@gemini-code-assist review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces the ability to set and edit stable, human-readable keys (labels) for multivariate feature variations in the frontend. It adds inline editing with validation for variant keys, tracks unsaved variations, updates UI components like Input and ContentCard to support these changes, and updates E2E tests and Storybook stories. A review comment correctly identified a bug in mergeEnvironmentWeights where using the logical OR operator (||) instead of nullish coalescing (??) would cause an environment weight of 0 to incorrectly fall back to the default percentage allocation.

Comment on lines +80 to +92
const mergeEnvironmentWeights = (options: any[], variations: any[]): any[] =>
options.map((v: any) => {
const matchingVariation = variations.find(
(e: any) => e.multivariate_feature_option === v.id,
)
return {
...v,
default_percentage_allocation:
(matchingVariation && matchingVariation.percentage_allocation) ||
v.default_percentage_allocation ||
0,
}
})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

In mergeEnvironmentWeights, using the logical OR operator (||) to fall back to v.default_percentage_allocation when matchingVariation.percentage_allocation is falsy introduces a bug. If the environment weight is explicitly set to 0 (which is falsy in JavaScript), it will incorrectly fall back to the default percentage allocation instead of preserving the 0 value.

Using nullish coalescing (??) resolves this issue by only falling back when the value is null or undefined.

Suggested change
const mergeEnvironmentWeights = (options: any[], variations: any[]): any[] =>
options.map((v: any) => {
const matchingVariation = variations.find(
(e: any) => e.multivariate_feature_option === v.id,
)
return {
...v,
default_percentage_allocation:
(matchingVariation && matchingVariation.percentage_allocation) ||
v.default_percentage_allocation ||
0,
}
})
const mergeEnvironmentWeights = (options: any[], variations: any[]): any[] =>
options.map((v: any) => {
const matchingVariation = variations.find(
(e: any) => e.multivariate_feature_option === v.id,
)
return {
...v,
default_percentage_allocation:
matchingVariation?.percentage_allocation ??
v.default_percentage_allocation ??
0,
}
})

@vercel vercel Bot requested a deployment to Preview – flagsmith-frontend-staging June 10, 2026 15:57 Abandoned

@talissoncosta talissoncosta left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks pretty @Zaimwa9 .

Thanks for addressing the new design and writing the stories.
I caught this on storybook:

  1. The input underlined variant does not work well on dark mode yet. Do you mind double check that ?
Image

Overall looks great!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature New feature or request front-end Issue related to the React Front End Dashboard

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants