Skip to content

lighten flag detail load and list add/edit for large lists#3215

Open
danoswaltCL wants to merge 2 commits into
release/6.5from
wip/large-ff-segment-ui-support
Open

lighten flag detail load and list add/edit for large lists#3215
danoswaltCL wants to merge 2 commits into
release/6.5from
wip/large-ff-segment-ui-support

Conversation

@danoswaltCL

@danoswaltCL danoswaltCL commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

#3210

From the broader #3208

This was tested with lists up to 20k.

The current details page for FF breaks because the current UI strategy has /flags/:id do a heavy load of everything about the flag and load it into ngrx store in order to support heavy request payloads to edit and enable lists. This eager get-and-send-everything for every CRUD operation doesn't scale.

This does a few things in tandem to break down

  • A new lightweight endpoint for details page flag operations that don't need all the data to do their job
  • Lazy-load the members of the lists on edit-modal load (new endpoint also)
  • A PATCH endpoint for toggling list status to mitigate sending a bunch of info in the request

It also fixes a couple of other things:

  • On backend, edit list still does an entire replacement, which has a bug in the sql has it delete every member individually. Ideally it would PATCH but to avoid making a large change larger, this works perfectly fine with a minor sql change to delete the whole thing before replacement.

  • For flags, the submit button for editing lists was not getting disabled, so it looks like nothing is happening and you can keep clicking and sending these giant requests. So I sewed that up in this PR also, it would crash the server. a separate ticket was filed for this: Upsert list modal allows double-clicks, multiple submits. #3209

Feature flags whose inclusion/exclusion lists have tens of thousands of members made the
details page, list edits, and enable/disable toggles slow or broken. This reworks those
paths to avoid loading and rewriting full member lists unnecessarily.

Detail page load:
- Add FeatureFlagService.findOneForDetails (counts-only via loadRelationCountAndMap, no
  member arrays, no Cartesian join) and use it for GET /flags/:id. findOne keeps loading
  full members for callers that need them (exports). updateList also uses the counts-only
  fetch since it only needs the flag id/name.
- Frontend renders individualForSegmentCount / groupForSegmentCount; drops the
  members-dependent values tooltip.

Editing a list:
- Add GET /segments/:id/members (getSegmentByIdWithMembers) that returns a segment,
  including private lists, with its members; the edit modal lazy-loads through it.
  getSegmentById still excludes private segments.
- Clear existing members on update with a single DELETE ... WHERE segmentId = :id per
  member table instead of a 20k-element per-row delete criteria (and drop the pre-SELECT).

Enable/disable toggle:
- Add PATCH /flags/{inclusion,exclusion}List/:id/status (updateListStatus) that flips only
  the enabled column without rewriting members; wire the inclusions toggle to it.

Add/Edit modal button:
- Set the upsert-loading flag on list update actions (not just add/delete), expose the
  correct feature-flag selector, and combine loading across the flag/experiment/segment
  stores so the primary button disables during any in-flight add or edit and can't be
  double-submitted.

Tests: unit coverage for findOneForDetails, updateListStatus, getSegmentByIdWithMembers,
delete-by-segmentId, and the new controller routes; the FeatureFlag inclusion/exclusion
integration case now also exercises the counts-only view, private members fetch, and
status toggle.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@danoswaltCL danoswaltCL requested a review from Copilot July 2, 2026 17:46
@danoswaltCL danoswaltCL changed the title perf(flags): lighten flag detail load and list add/edit for large lists lighten flag detail load and list add/edit for large lists Jul 2, 2026

Copilot AI 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.

Pull request overview

Improves performance and scalability of Feature Flag details and list operations for very large inclusion/exclusion lists by switching the details flow to counts-only loading, lazy-loading list members only when needed, and introducing lightweight status-toggle endpoints to avoid re-sending huge payloads.

Changes:

  • Backend: add findOneForDetails (counts-only) for flag details, add /segments/:id/members for lazy member fetch, and add status-only PATCH endpoints for inclusion/exclusion list enabled toggles.
  • Frontend: update list tables/modals to use member counts, lazy-load members on edit, and disable upsert actions while requests are in flight.
  • Backend: optimize list edit teardown to delete members via single DELETE ... WHERE segmentId = :id operations (avoids per-row OR predicates).

Reviewed changes

Copilot reviewed 30 out of 30 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
packages/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-details-participant-list-table/common-details-participant-list-table.component.ts Use count fields when available; remove tooltip-based preview logic.
packages/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-details-participant-list-table/common-details-participant-list-table.component.html Remove values tooltip usage to avoid requiring member arrays.
packages/frontend/projects/upgrade/src/app/features/dashboard/segments/modals/upsert-private-segment-list-modal/upsert-private-segment-list-modal.component.ts Lazy-load members for edit and unify “upsert in-flight” button disabling across stores.
packages/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/pages/feature-flag-details-page/feature-flag-details-page-content/feature-flag-inclusions-section-card/feature-flag-inclusions-section-card.component.ts Switch inclusion enable/disable to status-only updates (no member rewrite).
packages/frontend/projects/upgrade/src/app/core/segments/store/segments.model.ts Add optional member-count fields for counts-only segment loads.
packages/frontend/projects/upgrade/src/app/core/segments/segments.service.ts Add bypass-store member-fetch helper for edit modal.
packages/frontend/projects/upgrade/src/app/core/segments/segments.data.service.ts Add /segments/:id/members data call.
packages/frontend/projects/upgrade/src/app/core/feature-flags/store/feature-flags.selectors.ts Add selector for list upsert loading state.
packages/frontend/projects/upgrade/src/app/core/feature-flags/store/feature-flags.reducer.ts Track upsert-list loading and apply status-toggle success updates in store.
packages/frontend/projects/upgrade/src/app/core/feature-flags/store/feature-flags.effects.ts Add effects for status-only inclusion/exclusion toggles.
packages/frontend/projects/upgrade/src/app/core/feature-flags/store/feature-flags.actions.ts Add actions for inclusion/exclusion list status toggles.
packages/frontend/projects/upgrade/src/app/core/feature-flags/feature-flags.service.ts Wire new selectors/actions for list upsert loading and status toggles.
packages/frontend/projects/upgrade/src/app/core/feature-flags/feature-flags.data.service.ts Add PATCH calls for `/flags/{inclusion
packages/frontend/projects/upgrade/src/app/core/experiments/store/experiments.selectors.ts Add selector for experiment list-upsert loading flag.
packages/frontend/projects/upgrade/src/app/core/experiments/store/experiments.reducer.ts Track experiment list upsert loading state for modal button disabling.
packages/frontend/projects/upgrade/src/app/core/experiments/store/experiments.model.ts Add experiment list upsert loading field to state model.
packages/frontend/projects/upgrade/src/app/core/experiments/experiments.service.ts Expose experiment list-upsert loading observable.
packages/backend/src/api/services/SegmentService.ts Add getSegmentByIdWithMembers; optimize member teardown deletes for edits.
packages/backend/src/api/services/FeatureFlagService.ts Add findOneForDetails; use it in list update path; add updateListStatus.
packages/backend/src/api/models/Segment.ts Add non-persisted count fields for counts-only loads.
packages/backend/src/api/controllers/validators/FeatureFlagListValidator.ts Add validator for status-only PATCH body (enabled).
packages/backend/src/api/controllers/SegmentController.ts Add /segments/:segmentId/members route returning private segments with members.
packages/backend/src/api/controllers/FeatureFlagController.ts Route GET /flags/:id to counts-only; add status-only PATCH endpoints.
packages/backend/test/unit/services/SegmentService.test.ts Add unit coverage for members endpoint service method and delete-by-segmentId behavior.
packages/backend/test/unit/services/FeatureFlagService.test.ts Add unit coverage for findOneForDetails and updateListStatus.
packages/backend/test/unit/controllers/SegmentController.test.ts Add controller smoke test for /segments/:segmentId/members.
packages/backend/test/unit/controllers/mocks/SegmentServiceMock.ts Add mock implementation for getSegmentByIdWithMembers.
packages/backend/test/unit/controllers/mocks/FeatureFlagServiceMock.ts Add mock implementation for findOneForDetails and updateListStatus.
packages/backend/test/unit/controllers/FeatureFlagController.test.ts Add controller smoke tests for counts-only GET and status PATCH endpoints.
packages/backend/test/integration/FeatureFlags/FeatureFlagInclusionExclusion.ts Integration assertions for counts-only details, members endpoint, and status-only toggle behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/backend/src/api/services/FeatureFlagService.ts
Feature flags whose inclusion/exclusion lists have tens of thousands of members made the
details page, list edits, and enable/disable toggles slow or broken. This reworks those
paths to avoid loading and rewriting full member lists unnecessarily.

Detail page load:
- Add FeatureFlagService.findOneForDetails (counts-only via loadRelationCountAndMap, no
  member arrays, no Cartesian join) and use it for GET /flags/:id. findOne keeps loading
  full members for callers that need them (exports). updateList also uses the counts-only
  fetch since it only needs the flag id/name.
- Frontend renders individualForSegmentCount / groupForSegmentCount; drops the
  members-dependent values tooltip.

Editing a list:
- Add GET /segments/:id/members (getSegmentByIdWithMembers) that returns a segment,
  including private lists, with its members; the edit modal lazy-loads through it.
  getSegmentById still excludes private segments.
- Clear existing members on update with a single DELETE ... WHERE segmentId = :id per
  member table instead of a 20k-element per-row delete criteria (and drop the pre-SELECT).

Enable/disable toggle:
- Add PATCH /flags/{inclusion,exclusion}List/:id/status (updateListStatus) that flips only
  the enabled column without rewriting members; wire the inclusions toggle to it.

Add/Edit modal button:
- Set the upsert-loading flag on list update actions (not just add/delete), expose the
  correct feature-flag selector, and combine loading across the flag/experiment/segment
  stores so the primary button disables during any in-flight add or edit and can't be
  double-submitted.

Tests: unit coverage for findOneForDetails, updateListStatus, getSegmentByIdWithMembers,
delete-by-segmentId, and the new controller routes; the FeatureFlag inclusion/exclusion
integration case now also exercises the counts-only view, private members fetch, and
status toggle.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@danoswaltCL danoswaltCL force-pushed the wip/large-ff-segment-ui-support branch from 9dab810 to 6a38b88 Compare July 2, 2026 18:58
@danoswaltCL

Copy link
Copy Markdown
Collaborator Author

@copilot re-review based on a force pushed update. the update includes the overall flag enable/disable toggle in the operations that use the lighter payload, as this was found to be a slow operation in the path of the heavy .findOne method.

Copilot AI commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

@copilot re-review based on a force pushed update. the update includes the overall flag enable/disable toggle in the operations that use the lighter payload, as this was found to be a slow operation i...

Reviewed the force-pushed update. The additions look correct:

  • updateState (flag enable/disable toggle) now uses findOneForDetails — it only accesses context[0], name, and status, all of which are direct flag columns available in the counts-only load.
  • delete switched similarly — it only needs segment.id from the already-loaded join records (featureFlagSegmentInclusion/featureFlagSegmentExclusion) and name/context[0], none of which require member arrays.
  • updateFeatureFlagInDB (flag metadata update) uses findOneForDetails — it extracts segment.id from inclusion/exclusion join records (still present in the counts-only result) and context[0] for cache invalidation.
  • importLists switched — it only needs featureFlag.context[0] to set the new list's context.
  • Modal catchError on the lazy member fetch closes the modal on error, preventing a partially-initialized form from being saved with empty members.

The exportAllLists method correctly retains findOne since it must iterate the full member lists. CI checks that have completed (lint, frontend tests, build) are all passing.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants