Skip to content

feat: wire CSV segments to the cohorts API - #8295

Open
Zaimwa9 wants to merge 1 commit into
feat/cohort-csv-syncfrom
feat/cohort-csv-frontend
Open

feat: wire CSV segments to the cohorts API#8295
Zaimwa9 wants to merge 1 commit into
feat/cohort-csv-syncfrom
feat/cohort-csv-frontend

Conversation

@Zaimwa9

@Zaimwa9 Zaimwa9 commented Aug 14, 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

Wires the CSV segment drawer (#8283) to the cohorts API (#8294).

Create flow

  • New useCohort service: createCohort, syncCohortCsv (multipart) and deleteCohort mutations, invalidating the segment list.
  • The drawer submits in two steps: create the cohort, then sync the trimmed, headerless, single-column CSV built from the selected identifier column. Only the identifier column leaves the browser. If the sync fails, retrying reuses the created cohort instead of creating a duplicate segment.
  • Success toast reports the added count from the API and stays visible for 10 seconds.
  • Custom Fields (metadata) are sent with the create call.

Segment list

  • CSV-backed segments show a CSV chip and a chip with the targeted environment's name, driven by the cohort summary on the segment payload.
  • Removing a CSV segment routes through the cohort delete endpoint (the segment endpoint rejects cohort-managed segments); plain segments are unchanged.
  • Deletion is drain-then-delete on the API side, so while a cohort is awaiting drain the row is greyed out, cannot be opened, and has no actions.
  • Fixed the row action dropdown staying open under the confirm modal (it only closed on outside clicks).

Upload hardening

  • CsvUpload takes a maxSizeBytes prop; the drawer passes 10MB to mirror the API cap, so oversized files are rejected client-side with a clear message.
  • toParsedCsv no longer spreads every row into Math.max, which overflowed the call stack on large files.

How did you test this code?

  • Unit tests for the CSV utilities (17 passing), including quoting and column extraction.
  • Manual QA against a local API: happy path with multi-column and single-column files, count parity between the preview and the toast, metadata on create, oversized file rejection, CSV and plain segment deletion, the deleting state, and membership rows verified in the database.
image

@Zaimwa9
Zaimwa9 requested a review from a team as a code owner August 14, 2026 14:37
@Zaimwa9
Zaimwa9 requested review from kyle-ssg and talissoncosta and removed request for a team August 14, 2026 14:37
@vercel

vercel Bot commented Aug 14, 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 Aug 14, 2026 3:56pm
flagsmith-frontend-staging Ready Ready Preview Aug 14, 2026 3:56pm
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Ignored Ignored Preview Aug 14, 2026 3:56pm

Request Review

@coderabbitai

coderabbitai Bot commented Aug 14, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 26544d8a-09f0-4935-b951-1639081da11e

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Added typed cohort APIs for creation, deletion, and CSV synchronisation. Added cohort request and response contracts. Added CSV column serialisation and upload size validation. Updated the CSV modal to create cohorts and synchronise identifier columns. Updated segment removal to use cohort deletion when applicable. Segment rows now show cohort details and deletion state.

Estimated code review effort: 4 (Complex) | ~45 minutes

Merge Risk: 🟡 Moderate · up to f5c68

The PR adds CSV-backed cohort creation, synchronization, and deletion, but the current implementation can leave the UI stale, fail to remove a cohort during loading, or create a cohort whose upload is rejected when escaping expands the file beyond the API limit. Merge should wait for these bounded correctness issues to be addressed or explicitly accepted.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Docker builds report

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

@Zaimwa9

Zaimwa9 commented Aug 14, 2026

Copy link
Copy Markdown
Contributor Author

@themis-blindfold review

@Zaimwa9
Zaimwa9 removed the request for review from kyle-ssg August 14, 2026 14:41
@github-actions

github-actions Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor
✅ private-cloud · depot-ubuntu-latest-16 — run #19371 (attempt 1)

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

passed  1 passed

Details

stats  1 test across 1 suite
duration  1 minute, 1 second
commit  166aae9
info  🔄 Run: #19371 (attempt 1)

🗂️ Previous results
✅ private-cloud · depot-ubuntu-latest-arm-16 — run #19372 (attempt 1)

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

passed  1 passed

Details

stats  1 test across 1 suite
duration  1 minute, 2 seconds
commit  166aae9
info  🔄 Run: #19372 (attempt 1)

✅ private-cloud · depot-ubuntu-latest-arm-16 — run #19371 (attempt 1)

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

passed  3 passed

Details

stats  3 tests across 3 suites
duration  38.6 seconds
commit  166aae9
info  🔄 Run: #19371 (attempt 1)

✅ private-cloud · depot-ubuntu-latest-16 — run #19372 (attempt 1)

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

passed  3 passed

Details

stats  3 tests across 3 suites
duration  36.4 seconds
commit  166aae9
info  🔄 Run: #19372 (attempt 1)

✅ oss · depot-ubuntu-latest-16 — run #19372 (attempt 1)

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

passed  1 passed

Details

stats  1 test across 1 suite
duration  36.2 seconds
commit  166aae9
info  🔄 Run: #19372 (attempt 1)

✅ oss · depot-ubuntu-latest-arm-16 — run #19372 (attempt 1)

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

passed  1 passed

Details

stats  1 test across 1 suite
duration  39.8 seconds
commit  166aae9
info  🔄 Run: #19372 (attempt 1)

❌ oss · depot-ubuntu-latest-16 — run #19371 (attempt 1)

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

failed  1 failed

Details

stats  1 test across 1 suite
duration  21.6 seconds
commit  166aae9
info  📦 Artifacts: View test results and HTML report
🔄 Run: #19371 (attempt 1)

Failed tests

firefox › tests/segment-test.pw.ts › Segment test 4 - Create ANY rule type segment and verify match changes when rule is updated @oss

✅ private-cloud · depot-ubuntu-latest-16 — run #19363 (attempt 1)

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

passed  2 passed

Details

stats  2 tests across 2 suites
duration  1 minute, 5 seconds
commit  f5c68a3
info  🔄 Run: #19363 (attempt 1)

✅ oss · depot-ubuntu-latest-arm-16 — run #19363 (attempt 1)

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

passed  1 passed

Details

stats  1 test across 1 suite
duration  58.3 seconds
commit  f5c68a3
info  🔄 Run: #19363 (attempt 1)

✅ private-cloud · depot-ubuntu-latest-arm-16 — run #19363 (attempt 1)

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

passed  1 passed

Details

stats  1 test across 1 suite
duration  53.3 seconds
commit  f5c68a3
info  🔄 Run: #19363 (attempt 1)

✅ oss · depot-ubuntu-latest-16 — run #19363 (attempt 1)

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

passed  1 passed

Details

stats  1 test across 1 suite
duration  40.2 seconds
commit  f5c68a3
info  🔄 Run: #19363 (attempt 1)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 8b480a4e-0382-4485-8041-aa747fe926f9

📥 Commits

Reviewing files that changed from the base of the PR and between aa61a35 and f5c68a3.

📒 Files selected for processing (10)
  • frontend/common/services/useCohort.ts
  • frontend/common/types/requests.ts
  • frontend/common/types/responses.ts
  • frontend/common/utils/__tests__/csv.test.ts
  • frontend/common/utils/csv.ts
  • frontend/web/components/CsvUpload/CsvUpload.tsx
  • frontend/web/components/modals/ConfirmRemoveSegment.tsx
  • frontend/web/components/modals/CreateSegmentFromCsv/CreateSegmentFromCsv.tsx
  • frontend/web/components/segments/SegmentRow/SegmentRow.tsx
  • frontend/web/components/segments/SegmentRow/components/SegmentAction.tsx

Comment on lines +185 to +191
syncCohortCsv: {
environmentApiKey: string
cohortId: number
file: File
identifier_column?: number
has_header?: boolean
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Invalidate the Segment list after CSV synchronisation.

CSV synchronisation changes cohort membership. createCohort and deleteCohort invalidate LIST${projectId} for Segment, but syncCohortCsv cannot do this because its request has no projectId. Segment membership counts can remain stale after a successful upload.

  • frontend/common/types/requests.ts#L185-L191: add projectId to Req['syncCohortCsv'].
  • frontend/common/services/useCohort.ts#L39-L39: invalidate { id: \LIST${arg.projectId}`, type: 'Segment' }` with the Cohort tag.
  • frontend/web/components/modals/CreateSegmentFromCsv/CreateSegmentFromCsv.tsx#L113-L124: pass projectId: Number(projectId) to syncCohortCsv.
📍 Affects 3 files
  • frontend/common/types/requests.ts#L185-L191 (this comment)
  • frontend/common/services/useCohort.ts#L39-L39
  • frontend/web/components/modals/CreateSegmentFromCsv/CreateSegmentFromCsv.tsx#L113-L124

Comment on lines +27 to +33
segment.cohort && cohortEnvironmentApiKey
? await deleteCohort(getStore(), {
cohortId: segment.cohort.id,
environmentApiKey: cohortEnvironmentApiKey,
projectId: Number(projectId),
})
: await deleteSegment(getStore(), { id: segment.id, projectId })

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Do not fall back to deleteSegment for a cohort-managed segment.

If segment.cohort exists but cohortEnvironmentApiKey is unavailable, lines 27-33 call deleteSegment. The segment endpoint rejects cohort-managed segments. This occurs if the user opens the removal flow before the environment query completes.

Reject the removal until the cohort environment API key is available. Keep deleteSegment only for segments without a cohort.

Proposed fix
+      const cohort = segment.cohort
+      if (cohort && !cohortEnvironmentApiKey) {
+        throw new Error('Cohort environment API key is unavailable')
+      }
       const res =
-        segment.cohort && cohortEnvironmentApiKey
+        cohort
           ? await deleteCohort(getStore(), {
-              cohortId: segment.cohort.id,
+              cohortId: cohort.id,
               environmentApiKey: cohortEnvironmentApiKey,
               projectId: Number(projectId),
             })
           : await deleteSegment(getStore(), { id: segment.id, projectId })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
segment.cohort && cohortEnvironmentApiKey
? await deleteCohort(getStore(), {
cohortId: segment.cohort.id,
environmentApiKey: cohortEnvironmentApiKey,
projectId: Number(projectId),
})
: await deleteSegment(getStore(), { id: segment.id, projectId })
const cohort = segment.cohort
if (cohort && !cohortEnvironmentApiKey) {
throw new Error('Cohort environment API key is unavailable')
}
const res =
cohort
? await deleteCohort(getStore(), {
cohortId: cohort.id,
environmentApiKey: cohortEnvironmentApiKey,
projectId: Number(projectId),
})
: await deleteSegment(getStore(), { id: segment.id, projectId })

Comment thread frontend/web/components/modals/CreateSegmentFromCsv/CreateSegmentFromCsv.tsx Outdated
@github-actions

github-actions Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Visual Regression

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

@Zaimwa9
Zaimwa9 force-pushed the feat/cohort-csv-frontend branch from f5c68a3 to 5757a9d Compare August 14, 2026 14:48
@Zaimwa9
Zaimwa9 requested review from a team as code owners August 14, 2026 14:48
@Zaimwa9
Zaimwa9 requested review from matthewelwell and removed request for a team August 14, 2026 14:48
@github-actions github-actions Bot added api Issue related to the REST API docs Documentation updates labels Aug 14, 2026
@Zaimwa9
Zaimwa9 force-pushed the feat/cohort-csv-sync branch from aa61a35 to 4083994 Compare August 14, 2026 14:49
@Zaimwa9
Zaimwa9 removed the request for review from matthewelwell August 14, 2026 14:51
Comment thread frontend/web/components/modals/ConfirmRemoveSegment.tsx Outdated
@themis-blindfold

Copy link
Copy Markdown
Contributor

⚖️ Themis review: 🟠 Fix before merge

Wires the CSV segment drawer to the cohort API with a clean two-step create-then-sync flow and idempotent retry logic. The segment list gains cohort chips and pending-deletion state handling. One deletion path was missed: SegmentPage.tsx never received the cohort environment key, so deleting a cohort segment from the detail page always falls through to the rejected deleteSegment endpoint.

Area Score
🎯 Correctness 3/5
🧪 Test coverage 3/5
📐 Code quality 4/5
🚀 Product impact 4/5

🟠 Majors

  • frontend/web/components/modals/ConfirmRemoveSegment.tsx:22SegmentPage.tsx:35 never passes cohortEnvironmentApiKey, so cohort segment deletion from the detail page always fails with a confusing error toast. See inline.

🧹 Nits

  • frontend/common/services/useCohort.ts:39syncCohortCsv.invalidatesTags only targets the individual Cohort tag, not the Segment list. After a successful CSV sync the segment list shows stale membership counts until the next navigation. Adding projectId to the request type and invalidating { id: 'LIST${projectId}', type: 'Segment' } would fix it. An existing review thread covers the same concern.
📝 Walkthrough
  • Cohort service (useCohort.ts) — new RTK Query service with createCohort, syncCohortCsv (multipart via toFormData), and deleteCohort mutations; create and delete invalidate the segment list.
  • CSV drawer wiring (CreateSegmentFromCsv.tsx) — two-step save: create cohort, then sync the extracted identifier column as a headerless CSV. Tracks createdCohortId so a failed sync retries without creating a duplicate segment.
  • Segment list (SegmentRow.tsx) — cohort segments display source-type and environment chips; pending-deletion rows are greyed out with actions hidden. Deletion routes through deleteCohort when the cohort environment key is available.
  • Upload hardeningCsvUpload accepts maxSizeBytes (10 MB); toParsedCsv uses reduce instead of spreading into Math.max to avoid call-stack overflow on large files.
  • Dropdown fix (SegmentAction.tsx) — setIsOpen(false) before opening clone/remove modals so the action menu does not persist behind the modal.
🧪 How to verify
  1. Navigate to the segment detail page for a cohort-managed segment and click the delete button — confirm it either works or shows a clear error, not a generic "Error removing segment" toast.
  2. Create a CSV segment via the drawer; after the success toast, verify the segment list shows the correct membership count without a manual refresh.
  3. Upload a CSV near the 10 MB limit; verify the client-side rejection message appears before the upload.
  4. Open the segment list with a cohort segment pending deletion; confirm the row is greyed out, not clickable, and has no action menu.
  5. Click Clone or Remove on a regular segment; confirm the action dropdown closes before the modal opens.

Automate: add a unit/integration test that calls handleRemoveSegment for a segment with cohort set and cohortEnvironmentApiKey undefined, asserting the error is surfaced (or the action is blocked).

Product take: This delivers the end-to-end CSV segment flow — upload, parse, preview, create, sync — with thoughtful hardening (retry idempotency, client-side size cap, stack-overflow fix). Solid capability addition that closes the loop from PR #8283 and #8294. The gap in the detail-page deletion path should be fixed before shipping to avoid user confusion when deleting cohort segments.

🧭 Assumptions & unverified claims
  • Assumed toFormData serialises has_header: false as the string "false" and that DRF's BooleanField maps "false" to False. Confirmed in toFormData.ts; DRF mapping is standard behaviour but was not verified against the pinned DRF version.
  • Assumed the environment list query (useGetEnvironmentsQuery) is typically cache-warm from the project page, making the race window in SegmentRow narrow in practice.

A cohort's identifiers leave the browser but the detail page's delete button stays home. · reviewed at f5c68a3

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

Labels

api Issue related to the REST API 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.

1 participant