feat(webapp): admin UI for global and org-level feature flag overrides#3291
feat(webapp): admin UI for global and org-level feature flag overrides#3291
Conversation
…lls, extract constants
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughThis PR adds full admin feature-flag management: a new client dialog component (FeatureFlagsDialog) and reusable FlagControls, a global admin route (/admin/feature-flags) with loader/action, and a new org-level API (admin.api.v2.orgs.$organizationId.feature-flags). It extracts FEATURE_FLAG and validation helpers into a new v3/featureFlags.ts module, updates many imports to the new module, replaces several Prisma Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes 🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
… FEATURE_FLAG constant, fix findUnique
| const keysToDelete: string[] = []; | ||
| const upsertOps: ReturnType<typeof prisma.featureFlag.upsert>[] = []; | ||
|
|
||
| for (const key of catalogKeys) { | ||
| if (key in validatedFlags) { | ||
| upsertOps.push( | ||
| prisma.featureFlag.upsert({ | ||
| where: { key }, | ||
| create: { key, value: validatedFlags[key] as any }, | ||
| update: { value: validatedFlags[key] as any }, | ||
| }) | ||
| ); | ||
| } else { | ||
| keysToDelete.push(key); | ||
| } | ||
| } | ||
|
|
||
| await prisma.$transaction([ | ||
| ...upsertOps, | ||
| ...(keysToDelete.length > 0 | ||
| ? [prisma.featureFlag.deleteMany({ where: { key: { in: keysToDelete } } })] | ||
| : []), | ||
| ]); |
There was a problem hiding this comment.
🚩 Global flags action uses destructive replace-all semantics
The admin.feature-flags.tsx action at lines 86-108 iterates ALL catalog keys and deletes any feature flag row not present in the submitted payload. This means saving from the global flags UI is a full replace operation — any flag not explicitly set in the UI form will be deleted from the featureFlag table. This is by design (replace semantics documented in the route comments) but has significant operational implications: if the defaultWorkerInstanceGroupId flag is accidentally unset, RegionsPresenter.server.ts:56-58 will throw an error ("Default worker instance group not found"), breaking the regions page for all users. Consider adding a confirmation or guard for critical flags like defaultWorkerInstanceGroupId that other parts of the system depend on being present.
Was this helpful? React with 👍 or 👎 to provide feedback.
0ski
left a comment
There was a problem hiding this comment.
Nice, that's going to be useful!
Adds a dialog to the admin orgs page for viewing and editing per-org feature flag overrides. Flags are introspected from the catalog so the UI stays in sync with available flags automatically. Also adds a new tab for global flags.
Refactors featureFlags.server.ts to split catalog definition (shared) from server-only runtime (flags(), makeSetMultipleFlags). The shared module exports flag metadata and validation so both the UI and API routes can use it without pulling in server dependencies.
Org-level modal:
Global flags confirmation modal: