Skip to content

feat(SCIM): Display deactivated org memberships - #8649

Merged
khvn26 merged 4 commits into
mainfrom
feat/members-list-inactive-membership
Oct 1, 2026
Merged

khvn26 merged 4 commits into
mainfrom
feat/members-list-inactive-membership

Conversation

@khvn26

@khvn26 khvn26 commented Oct 1, 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

Closes #8648

Follow-up to #8368 / #8370, which introduced deactivated organisation memberships via SCIM.

API

  • GET /api/v1/organisations/{organisation_pk}/users/ (and the update-role response) now expose is_organisation_membership_active. It's read from the already-prefetched userorganisation_set, so the list query count is unchanged.

Frontend

  • New InactiveMembershipChip component: an Inactive chip (base Chip) with a tooltip explaining that the membership was deactivated by the identity provider, that the member can't access the organisation and doesn't count towards the seat limit, and that their role, permissions and groups are retained. It only renders when the field is explicitly false.
  • The chip is shown wherever organisation users are listed or picked:
    • Users and Permissions → Members table
    • UserSelect (flag owners, project/environment admins, change request assignees, role members)
    • Group modal: "Add a user" dropdown and member rows
    • Project/environment permissions → Users tab
  • Inactive members remain selectable; their permissions are retained, so they're only labelled.

Docs

  • SCIM docs now describe how deactivated memberships appear in the dashboard and API, and why the listed member count can exceed seats in use.

Screenshots

1. Users and Permissions → Members (tooltip shown on hover)

Members list with Inactive chip and tooltip

2. UserSelect — Create Project → Project administrators

UserSelect with Inactive chip

3. Group modal — "Add a user" dropdown and member rows

Group modal add-user dropdown with Inactive chip Group modal member row with Inactive chip

4. Project Settings → Permissions → Users

Project permissions users tab with Inactive chip

How did you test this code?

  • Unit tests for the list and update-role endpoints covering active and deactivated memberships; existing N+1 query-count test still passes.
  • Manually, against a local API with an organisation containing an admin, an active member and a member deactivated via set_organisation_membership_active(..., is_active=False):
    1. Users and Permissions → Members: the deactivated member shows an Inactive chip; hovering shows the tooltip.
    2. Groups → Create Group: chip shows in the "Add a user" dropdown and in the member rows once added.
    3. Project Settings → Permissions → Users: chip shows on the deactivated member.
    4. Create Project → Project administrators → Users (UserSelect): chip shows in the picker.

Expose `is_organisation_membership_active` on the organisation users
endpoint, and mark inactive members with an `Inactive` chip wherever
organisation users are listed or picked in the dashboard.

Closes #8648
@vercel

vercel Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

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

3 Skipped Deployments
Project Deployment Actions Updated
docs Ignored Ignored Preview Oct 1, 2026 4:25pm UTC
flagsmith-frontend-preview Ignored Ignored Preview Oct 1, 2026 4:25pm UTC
flagsmith-frontend-staging Ignored Ignored Preview Oct 1, 2026 4:25pm UTC

Request Review

@khvn26
khvn26 requested review from a team as code owners October 1, 2026 14:33
@khvn26 khvn26 self-assigned this Oct 1, 2026
@khvn26
khvn26 requested review from bakirFS, emyller and talissoncosta and removed request for a team October 1, 2026 14:33
@github-actions github-actions Bot added front-end Issue related to the React Front End Dashboard api Issue related to the REST API docs Documentation updates and removed docs Documentation updates labels Oct 1, 2026
@github-actions

github-actions Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Docker builds report

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

@coderabbitai

coderabbitai Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

🧰 Additional context used
📚 Code guidelines (1)
frontend/CLAUDE.md — auto-discovered
📝 Walkthrough

Walkthrough

The organisation users API now returns is_organisation_membership_active for each user. Tests cover active and inactive memberships, including role updates that leave a membership inactive. The frontend adds an Inactive chip to organisation user lists and selection views when the status is false. The SCIM documentation describes the chip, membership reactivation, and seat-limit behaviour.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: 🟡 Moderate · up to 202f2

The organisation-users API exposes the status under a different key than the one required by the issue, so clients expecting membership_active cannot read it. Align the field and its references before merging.

Security Architecture Review

Security architecture risk: 🔵 Low · up to 202f2

The change displays existing membership state without granting access or changing activation rules. Organisation access checks remain in place, and inactive members remain administratively manageable. No introduced security issue was identified; concurrent membership changes and deployment compatibility were not fully verified.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The added disclosure is membership-activity metadata for users in the requested organisation. Existing list permissions make it available to active organisation members, not only administrators; the inspected change does not broaden the organisation target scope.

Trust Boundaries and Controls

  • observed — The endpoint requires authentication. Listing requires active organisation membership or active organisation-admin status; role updates additionally require organisation-admin authority. Inactive memberships fail both active-membership checks, even when their stored role is admin.

Resilience and Maintainability Implications

  • observed — The role-update flow validates input before saving and then serializes the organisation-scoped user. The PR adds response observation rather than a new lifecycle step or recovery mechanism; the sequential test does not establish isolation from concurrent activation changes.
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


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.

@codecov

codecov Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.84%. Comparing base (df5e8a2) to head (202f2b7).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #8649   +/-   ##
=======================================
  Coverage   98.84%   98.84%           
=======================================
  Files        1659     1659           
  Lines       68541    68556   +15     
=======================================
+ Hits        67751    67766   +15     
  Misses        790      790           

☔ 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 commented Oct 1, 2026 •

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

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

passed  4 passed

Details

stats  4 tests across 4 suites
duration  4.1 seconds
commit  6423d8d
info  🔄 Run: #21098 (attempt 1)

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

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

passed  3 passed

Details

stats  3 tests across 3 suites
duration  38.3 seconds
commit  1e8ad28
info  🔄 Run: #21099 (attempt 1)

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

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

passed  1 passed

Details

stats  1 test across 1 suite
duration  38.2 seconds
commit  202f2b7
info  🔄 Run: #21100 (attempt 1)

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

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

passed  4 passed

Details

stats  4 tests across 4 suites
duration  11.5 seconds
commit  202f2b7
info  🔄 Run: #21100 (attempt 1)

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

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

passed  3 passed

Details

stats  3 tests across 3 suites
duration  10.7 seconds
commit  1e8ad28
info  🔄 Run: #21099 (attempt 1)

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

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

passed  2 passed

Details

stats  2 tests across 2 suites
duration  48.7 seconds
commit  6423d8d
info  🔄 Run: #21098 (attempt 1)

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

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

passed  1 passed

Details

stats  1 test across 1 suite
duration  37.3 seconds
commit  202f2b7
info  🔄 Run: #21100 (attempt 1)

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

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

passed  1 passed

Details

stats  1 test across 1 suite
duration  40.3 seconds
commit  6423d8d
info  🔄 Run: #21098 (attempt 1)

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

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

passed  1 passed

Details

stats  1 test across 1 suite
duration  36.9 seconds
commit  1e8ad28
info  🔄 Run: #21099 (attempt 1)

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

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

passed  1 passed

Details

stats  1 test across 1 suite
duration  31.4 seconds
commit  202f2b7
info  🔄 Run: #21100 (attempt 1)

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

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

passed  1 passed

Details

stats  1 test across 1 suite
duration  32.1 seconds
commit  6423d8d
info  🔄 Run: #21098 (attempt 1)

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

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

passed  1 passed

Details

stats  1 test across 1 suite
duration  31.4 seconds
commit  1e8ad28
info  🔄 Run: #21099 (attempt 1)

@github-actions

github-actions Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Visual Regression

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

@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.

Had a look at the frontend side. Looks pretty good!

One thing I would change, and one to take or leave.

Spacing. The chip owning its own margin means a passed className replaces it rather than adds to it, so the mr-2 on the group member row loses the left margin. I'd move the spacing onto the parents instead:

<div className='d-flex align-items-center gap-2'>
  <span>{`${first_name} ${last_name}`}</span>
  <InactiveMembershipChip user={user} />
</div>

I have it applied and checked across all five surfaces against a seeded org. Want me to push it to the branch?

Minor: UserSelect types users: any[], so the chip receives an untyped object. There is now a reason to narrow it to User[]. Easy to fold into the push above if you want it.

Drop the chip's built-in margin, and lay out name and chip with
`d-flex align-items-center gap-2` on each parent, so spacing is
consistent across surfaces. Narrow `UserSelect`'s `users` prop to
`User[]`.
@khvn26

khvn26 commented Oct 1, 2026

Copy link
Copy Markdown
Member Author

Thanks @talissoncosta! Fixed both in 6423d8d.

@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 Oct 1, 2026

@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 good! There's still a detail I missed on my first pass.

Image

However I realised that we are using a dead class that need to be fixed. So I will take it myself on a pr to re-introduce text-truncate

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to GitHub limitations.

⚠️ Outside diff range comments (1)

🟡 Minor · Expose the required membership_active API field. · serializers.py:69-76

api/users/serializers.py:69-76
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Expose the required membership_active API field.

Issue #8648 explicitly requires membership_active in the organisation users API. UserListSerializer emits only is_organisation_membership_active, so clients that follow the issue contract cannot read the membership status.

Rename the field and the same-PR frontend, test, and documentation references.

Suggested fix
-    is_organisation_membership_active = serializers.SerializerMethodField(
+    membership_active = serializers.SerializerMethodField(
...
-        "is_organisation_membership_active",
+        "membership_active",
...
-    def get_is_organisation_membership_active(self, instance: FFAdminUser) -> bool:
+    def get_membership_active(self, instance: FFAdminUser) -> bool:

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Advanced

Run ID: 2c4d8443-c782-4d58-be50-b9cf59019bb5

📥 Commits

Reviewing files that changed from the base of the PR and between 65d21c8 and 202f2b7.

📒 Files selected for processing (7)
  • api/tests/unit/users/test_unit_users_views.py
  • frontend/web/components/EditPermissions.tsx
  • frontend/web/components/UserSelect.tsx
  • frontend/web/components/modals/CreateGroup.tsx
  • frontend/web/components/modals/CreateRole.tsx
  • frontend/web/components/users-permissions/InactiveMembershipChip.tsx
  • frontend/web/components/users-permissions/OrganisationUsersTable/components/OrganisationUsersTableRow.tsx

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 7 remain after this review.

talissoncosta added a commit that referenced this pull request Oct 1, 2026
.text-truncate is defined in bootstrap/scss/helpers/_text-truncation.scss,
and we import type, containers, grid and the rest but never helpers. So the
class has never existed, and every call site using it silently does nothing.

Three components already use it, each with the surrounding markup written
correctly for it: AudienceSegmentList twice and CsvUpload once. They start
truncating with an ellipsis instead of overflowing, which is what their
markup was always asking for.

Refs #8649

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comment thread frontend/web/components/UserSelect.tsx

@emyller emyller 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.

LGTM from backend.

Comment thread api/tests/unit/users/test_unit_users_views.py Outdated
@khvn26
khvn26 merged commit 5853bc1 into main Oct 1, 2026
39 checks passed
@khvn26
khvn26 deleted the feat/members-list-inactive-membership branch October 1, 2026 17:47
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.

Members list in Users and Permission tab should clearly display deactivated org memberships

4 participants