Skip to content

feat(cohorts): add environment cohort CRUD API - #8248

Open
gagantrivedi wants to merge 12 commits into
mainfrom
feat/cohort-crud
Open

feat(cohorts): add environment cohort CRUD API#8248
gagantrivedi wants to merge 12 commits into
mainfrom
feat/cohort-crud

Conversation

@gagantrivedi

@gagantrivedi gagantrivedi commented Aug 10, 2026

Copy link
Copy Markdown
Member

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

Contributes to cohort sync (follow-up to #8211, #8212, #8213).

Adds the environment-scoped cohort CRUD API:

  • POST/GET/DELETE on /api/v1/environments/<api_key>/cohorts/; create builds a managed segment with a single IS_SET condition on the cohort's system trait key.
  • Deletion drains memberships from the identity store before soft-deleting: rows flip to pending_remove and the applier finishes the soft delete once drained (deletion_requested_at marks the wait).
  • Segments backing a live cohort reject direct dashboard edits and deletes (403).
  • Non-edge projects are rejected with 400 DynamoNotEnabledError (edge-identities precedent) — core/Postgres identity support branches out later.
  • The API requires the start-up plan or above (require_minimum_plan; self-hosted deployments are exempt as usual), plus MANAGE_SEGMENTS to create/delete.
  • cohort.created / cohort.deletion_requested / cohort.deleted events.

How did you test this code?

Unit tests for the create/delete services, applier task deletion branch, views, permissions (including plan gating via saas_mode), and segment edit protection; the full segments view suite still passes.

@vercel

vercel Bot commented Aug 10, 2026

Copy link
Copy Markdown

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

3 Skipped Deployments
Project Deployment Actions Updated (UTC)
docs Ignored Ignored Preview Aug 11, 2026 5:01am
flagsmith-frontend-preview Ignored Ignored Preview Aug 11, 2026 5:01am
flagsmith-frontend-staging Ignored Ignored Preview Aug 11, 2026 5:01am

Request Review

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 95cd17d4-c786-4e20-b467-f52ef913d8a2

📥 Commits

Reviewing files that changed from the base of the PR and between b0d1659 and 644d887.

📒 Files selected for processing (3)
  • api/cohorts/services.py
  • api/cohorts/views.py
  • docs/docs/deployment-self-hosting/observability/_events-catalogue.md
📝 Walkthrough

Walkthrough

Adds environment-scoped cohort CRUD endpoints with Startup plan and environment permission checks. Adds cohort creation with linked segments, rules, and conditions. Adds deferred deletion that drains memberships before removing the cohort and segment. Excludes deletion-pending cohorts from listings. Prevents changes to cohort-managed segments. Adds database migration, OpenAPI definitions, event catalogue entries, and unit tests.

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


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 added api Issue related to the REST API docs Documentation updates feature New feature or request and removed docs Documentation updates labels Aug 10, 2026
@github-actions github-actions Bot added the docs Documentation updates label Aug 10, 2026
@codecov

codecov Bot commented Aug 10, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.67%. Comparing base (377f203) to head (b0d1659).
⚠️ Report is 4 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #8248      +/-   ##
==========================================
- Coverage   98.72%   98.67%   -0.05%     
==========================================
  Files        1543     1565      +22     
  Lines       61683    62167     +484     
==========================================
+ Hits        60895    61344     +449     
- Misses        788      823      +35     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions github-actions Bot added feature New feature or request and removed feature New feature or request docs Documentation updates labels Aug 10, 2026
@github-actions github-actions Bot added docs Documentation updates feature New feature or request and removed feature New feature or request docs Documentation updates labels Aug 10, 2026
@github-actions github-actions Bot added docs Documentation updates feature New feature or request and removed feature New feature or request docs Documentation updates labels Aug 10, 2026
@github-actions github-actions Bot added docs Documentation updates and removed feature New feature or request docs Documentation updates labels Aug 10, 2026
@gagantrivedi
gagantrivedi marked this pull request as ready for review August 10, 2026 08:23
@gagantrivedi
gagantrivedi requested review from a team as code owners August 10, 2026 08:23
@gagantrivedi
gagantrivedi requested review from khvn26 and removed request for a team August 10, 2026 08:23
@github-actions github-actions Bot removed feature New feature or request docs Documentation updates labels Aug 10, 2026
@github-actions

github-actions Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Docker builds report

Image Build Status Security report
ghcr.io/flagsmith/flagsmith-e2e:pr-8248 Finished ✅ Skipped

@github-actions github-actions Bot added the feature New feature or request label Aug 10, 2026

@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: 4


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 42822bd2-7789-4d4b-a766-f0fcf54c1e7b

📥 Commits

Reviewing files that changed from the base of the PR and between eeac6b3 and b3b7744.

📒 Files selected for processing (17)
  • api/cohorts/migrations/0002_cohort_deletion_requested_at.py
  • api/cohorts/models.py
  • api/cohorts/permissions.py
  • api/cohorts/serializers.py
  • api/cohorts/services.py
  • api/cohorts/tasks.py
  • api/cohorts/urls.py
  • api/cohorts/views.py
  • api/environments/urls.py
  • api/segments/views.py
  • api/tests/unit/cohorts/test_permissions.py
  • api/tests/unit/cohorts/test_services.py
  • api/tests/unit/cohorts/test_tasks.py
  • api/tests/unit/cohorts/test_views.py
  • api/tests/unit/segments/test_unit_segments_views.py
  • docs/docs/deployment-self-hosting/observability/_events-catalogue.md
  • openapi.yaml

Comment thread api/cohorts/services.py
Comment thread api/cohorts/services.py
Comment on lines +125 to +134
def finalise_cohort_deletion(cohort: Cohort) -> None:
segment = cohort.segment
with transaction.atomic():
cohort.delete()
delete_segment(segment, AuthorData())
logger.info(
"cohort.deleted",
cohort__id=cohort.id,
environment__id=cohort.environment_id,
)

@coderabbitai coderabbitai Bot Aug 10, 2026

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 | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

For Django 5.0, after Model.delete() succeeds, is the deleted model instance's primary key set to None?

💡 Result:

Yes, for Django 5.0, after a model instance's delete method succeeds, the instance's primary key is set to None [1]. According to the official Django documentation, while the Python instance continues to exist after the delete method is called and retains the data in its other fields, the primary key attribute is explicitly set to None [1]. This behavior is implemented in the Django source code to reflect that the object no longer exists in the database [2][3]. Note that this can cause issues if your model's str method or other logic assumes the primary key remains populated after deletion [4].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate files =="
fd -a 'services.py|test.*cohort|cohort.*test' . | sed 's#^\./##' | head -100

echo
echo "== api/cohorts/services.py outline and relevant lines =="
ast-grep outline api/cohorts/services.py --view compact || true
sed -n '1,180p' api/cohorts/services.py | cat -n

echo
echo "== search cohort.deleted and finalise_cohort_deletion tests/usages =="
rg -n "cohort\.deleted|finalise_cohort_deletion|cohort__id|Cohort\.delete|delete_cohort" .

Repository: Flagsmith/flagsmith

Length of output: 10979


🌐 Web query:

documentation docs.djangoproject.com reference model instances after delete primary key set to None

💡 Result:

In Django, when the delete method is called on a model instance, the object is deleted from the database, but the Python instance itself remains in memory [1][2]. As a side effect of this operation, Django sets the instance's primary key attribute (and the pk proxy attribute) to None [1][3]. This behavior is explicitly documented in the official Django Model instance reference [1][4]. The documentation states that after calling delete, the Python instance will continue to exist and retain its field data, with the exception that its primary key is set to None [1][5]. This behavior ensures that the instance is no longer treated as an existing record in the database, preventing accidental attempts to update a non-existent row [1][4]. For developers, this means that any logic relying on the primary key—such as custom str methods or code that checks if an instance is "deleted"—must account for the possibility that the primary key may be None [6]. Top results: [1][7][3]

Citations:


Keep the cohort identifier before deletion.

Model.delete() leaves the Python instance in memory but sets its primary key to None; line 132 therefore emits cohort.deleted with cohort__id=None. Capture the identifier before deletion and use it for the event. The tests compare cohort.deleted against the deleted instance primary key, so they also do not catch this.

Proposed fix
 def finalise_cohort_deletion(cohort: Cohort) -> None:
+    cohort_id = cohort.id
     segment = cohort.segment
     with transaction.atomic():
         cohort.delete()
         delete_segment(segment, AuthorData())
     logger.info(
         "cohort.deleted",
-        cohort__id=cohort.id,
+        cohort__id=cohort_id,
         environment__id=cohort.environment_id,
     )
📝 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
def finalise_cohort_deletion(cohort: Cohort) -> None:
segment = cohort.segment
with transaction.atomic():
cohort.delete()
delete_segment(segment, AuthorData())
logger.info(
"cohort.deleted",
cohort__id=cohort.id,
environment__id=cohort.environment_id,
)
def finalise_cohort_deletion(cohort: Cohort) -> None:
cohort_id = cohort.id
segment = cohort.segment
with transaction.atomic():
cohort.delete()
delete_segment(segment, AuthorData())
logger.info(
"cohort.deleted",
cohort__id=cohort_id,
environment__id=cohort.environment_id,
)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Cohort is a SoftDeleteObject, so .delete() here soft-deletes (sets deleted_at and saves) — the pk survives. test_apply_cohort_membership_deltas__deletion_requested__finalises_after_drain asserts the emitted cohort__id against an instance the task never loaded, which would fail if the pk were cleared.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Cohort model =="
ast-grep outline api/cohorts/models.py --view expanded || true
sed -n '1,220p' api/cohorts/models.py | cat -n

echo
echo "== SoftDeleteObject definitions and delete overrides =="
rg -n -A20 -B8 'class\s+SoftDeleteObject\b|def\s+delete\s*\(' api --glob '*.py'

Length of output: 15299


@gagantrivedi, confirmed. Cohort inherits from SoftDeleteExportableModel, so the hard-delete assumption was incorrect. The cohort.deleted event can use cohort.id after cohort.delete().


✏️ Learnings added
Learnt from: gagantrivedi
Repo: Flagsmith/flagsmith PR: 8248
File: api/cohorts/services.py:124-133
Timestamp: 2026-08-11T04:59:37.878Z
Learning: In `api/cohorts/models.py`, `Cohort` inherits from `core.models.SoftDeleteExportableModel`. Its `delete()` operation soft-deletes the cohort by setting `deleted_at` and saving the model, so `cohort.id` remains available after deletion. Reviews of `api/cohorts/services.py:finalise_cohort_deletion` must not apply Django hard-delete primary-key-clearing behaviour to `Cohort`.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

You are interacting with an AI system.

Comment thread api/cohorts/views.py
Comment thread openapi.yaml
Comment on lines +2227 to +2229
responses:
'204':
description: No response body

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 | ⚡ Quick win

Document the actual delete response status.

CohortViewSet.destroy returns HTTP 202, but this contract documents HTTP 204. Update the specification so generated clients accept the deletion-request response.

Proposed fix
-        '204':
-          description: No response body
+        '202':
+          description: Deletion requested
📝 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
responses:
'204':
description: No response body
responses:
'202':
description: Deletion requested

@github-actions

github-actions Bot commented Aug 10, 2026

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

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

passed  2 passed

Details

stats  2 tests across 2 suites
duration  1 minute, 9 seconds
commit  b0d1659
info  🔄 Run: #19147 (attempt 1)

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

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

passed  4 passed

Details

stats  4 tests across 4 suites
duration  32.7 seconds
commit  b0d1659
info  🔄 Run: #19147 (attempt 1)

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

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

passed  1 passed

Details

stats  1 test across 1 suite
duration  46 seconds
commit  b0d1659
info  🔄 Run: #19147 (attempt 1)

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

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

passed  1 passed

Details

stats  1 test across 1 suite
duration  33.4 seconds
commit  b0d1659
info  🔄 Run: #19147 (attempt 1)

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

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

passed  2 passed

Details

stats  2 tests across 2 suites
duration  56.7 seconds
commit  b3b7744
info  🔄 Run: #19145 (attempt 1)

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

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

passed  3 passed

Details

stats  3 tests across 3 suites
duration  1 minute, 14 seconds
commit  b3b7744
info  🔄 Run: #19145 (attempt 1)

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

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

passed  1 passed

Details

stats  1 test across 1 suite
duration  37.7 seconds
commit  b3b7744
info  🔄 Run: #19145 (attempt 1)

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

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

passed  1 passed

Details

stats  1 test across 1 suite
duration  39.9 seconds
commit  b3b7744
info  🔄 Run: #19145 (attempt 1)

@github-actions

github-actions Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Visual Regression

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

@gagantrivedi
gagantrivedi removed the request for review from khvn26 August 10, 2026 08:40
@github-actions github-actions Bot added the docs Documentation updates label Aug 10, 2026
@github-actions github-actions Bot added feature New feature or request and removed feature New feature or request docs Documentation updates labels Aug 10, 2026

@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: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 7019dcdd-b1c1-4707-9d27-04040f0be7c2

📥 Commits

Reviewing files that changed from the base of the PR and between b3b7744 and b0d1659.

📒 Files selected for processing (7)
  • api/cohorts/services.py
  • api/cohorts/tasks.py
  • api/cohorts/views.py
  • api/tests/unit/cohorts/test_services.py
  • api/tests/unit/cohorts/test_tasks.py
  • api/tests/unit/cohorts/test_views.py
  • docs/docs/deployment-self-hosting/observability/_events-catalogue.md

Comment thread api/cohorts/views.py
@github-actions github-actions Bot added the docs Documentation updates label Aug 11, 2026
@github-actions github-actions Bot added feature New feature or request and removed feature New feature or request docs Documentation updates labels Aug 11, 2026
@github-actions github-actions Bot added the docs Documentation updates label Aug 11, 2026
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 docs Documentation updates feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant