Skip to content

Commit 9eaae9d

Browse files
committed
refactor: simplify dialog - drop unnecessary hooks, parallelize DB calls, extract constants
1 parent f081022 commit 9eaae9d

4 files changed

Lines changed: 76 additions & 90 deletions

File tree

apps/webapp/app/components/admin/FeatureFlagsDialog.tsx

Lines changed: 24 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useFetcher } from "@remix-run/react";
2-
import { useCallback, useEffect, useMemo, useState } from "react";
2+
import { useEffect, useMemo, useState } from "react";
33
import {
44
Dialog,
55
DialogContent,
@@ -14,6 +14,10 @@ import { Input } from "~/components/primitives/Input";
1414
import { cn } from "~/utils/cn";
1515
import type { FlagControlType } from "~/v3/featureFlags.server";
1616

17+
const UNSET_VALUE = "__unset__";
18+
19+
const HIDDEN_FLAGS = ["defaultWorkerInstanceGroupId"];
20+
1721
type LoaderData = {
1822
org: { id: string; title: string; slug: string };
1923
orgFlags: Record<string, unknown>;
@@ -42,18 +46,15 @@ export function FeatureFlagsDialog({
4246
const loadFetcher = useFetcher<LoaderData>();
4347
const saveFetcher = useFetcher<ActionData>();
4448

45-
// Local state for edits - keyed by flag name, value is the override or undefined (unset)
4649
const [overrides, setOverrides] = useState<Record<string, unknown>>({});
4750
const [initialOverrides, setInitialOverrides] = useState<Record<string, unknown>>({});
4851

49-
// Load flags when dialog opens
5052
useEffect(() => {
5153
if (open && orgId) {
5254
loadFetcher.load(`/admin/api/orgs/${orgId}/feature-flags`);
5355
}
5456
}, [open, orgId]);
5557

56-
// Sync loaded data into local state
5758
useEffect(() => {
5859
if (loadFetcher.data) {
5960
const loaded = loadFetcher.data.orgFlags ?? {};
@@ -62,7 +63,6 @@ export function FeatureFlagsDialog({
6263
}
6364
}, [loadFetcher.data]);
6465

65-
// Close on successful save
6666
useEffect(() => {
6767
if (saveFetcher.data?.success) {
6868
onOpenChange(false);
@@ -73,45 +73,40 @@ export function FeatureFlagsDialog({
7373
return JSON.stringify(overrides) !== JSON.stringify(initialOverrides);
7474
}, [overrides, initialOverrides]);
7575

76-
const setFlagValue = useCallback((key: string, value: unknown) => {
76+
const setFlagValue = (key: string, value: unknown) => {
7777
setOverrides((prev) => ({ ...prev, [key]: value }));
78-
}, []);
78+
};
7979

80-
const unsetFlag = useCallback((key: string) => {
80+
const unsetFlag = (key: string) => {
8181
setOverrides((prev) => {
8282
const next = { ...prev };
8383
delete next[key];
8484
return next;
8585
});
86-
}, []);
86+
};
8787

88-
const handleSave = useCallback(() => {
88+
const handleSave = () => {
8989
if (!orgId) return;
90-
9190
const body = Object.keys(overrides).length === 0 ? null : overrides;
92-
9391
saveFetcher.submit(JSON.stringify(body), {
9492
method: "POST",
9593
action: `/admin/api/orgs/${orgId}/feature-flags`,
9694
encType: "application/json",
9795
});
98-
}, [orgId, overrides, saveFetcher]);
96+
};
9997

10098
const data = loadFetcher.data;
10199
const isLoading = loadFetcher.state === "loading";
102100
const isSaving = saveFetcher.state === "submitting";
103101

104-
// Build JSON preview
105-
const jsonPreview = useMemo(() => {
106-
if (Object.keys(overrides).length === 0) return "null";
107-
return JSON.stringify(overrides, null, 2);
108-
}, [overrides]);
102+
const jsonPreview =
103+
Object.keys(overrides).length === 0 ? "null" : JSON.stringify(overrides, null, 2);
109104

110-
// Sort flag keys alphabetically
111-
const sortedFlagKeys = useMemo(() => {
112-
if (!data) return [];
113-
return Object.keys(data.controlTypes).sort();
114-
}, [data]);
105+
const sortedFlagKeys = data
106+
? Object.keys(data.controlTypes)
107+
.filter((key) => !HIDDEN_FLAGS.includes(key))
108+
.sort()
109+
: [];
115110

116111
return (
117112
<Dialog open={open} onOpenChange={onOpenChange}>
@@ -126,9 +121,7 @@ export function FeatureFlagsDialog({
126121
<div className="py-8 text-center text-sm text-text-dimmed">Loading flags...</div>
127122
) : data ? (
128123
<div className="flex flex-col gap-1.5">
129-
{sortedFlagKeys
130-
.filter((key) => key !== "defaultWorkerInstanceGroupId")
131-
.map((key) => {
124+
{sortedFlagKeys.map((key) => {
132125
const control = data.controlTypes[key];
133126
const isOverridden = key in overrides;
134127
const globalValue = data.globalFlags[key as keyof typeof data.globalFlags];
@@ -179,7 +172,7 @@ export function FeatureFlagsDialog({
179172
value={isOverridden ? (overrides[key] as string) : undefined}
180173
options={control.options}
181174
onChange={(val) => {
182-
if (val === "__unset__") {
175+
if (val === UNSET_VALUE) {
183176
unsetFlag(key);
184177
} else {
185178
setFlagValue(key, val);
@@ -210,7 +203,6 @@ export function FeatureFlagsDialog({
210203
) : null}
211204
</div>
212205

213-
{/* JSON Preview */}
214206
{data && (
215207
<details className="mt-2">
216208
<summary className="cursor-pointer text-xs text-text-dimmed hover:text-text-bright">
@@ -271,21 +263,21 @@ function EnumControl({
271263
onChange: (val: string) => void;
272264
dimmed: boolean;
273265
}) {
274-
const items = ["__unset__", ...options];
266+
const items = [UNSET_VALUE, ...options];
275267

276268
return (
277269
<Select
278270
variant="tertiary/small"
279-
value={value ?? "__unset__"}
271+
value={value ?? UNSET_VALUE}
280272
setValue={onChange}
281273
items={items}
282-
text={(val) => (val === "__unset__" ? "unset" : val)}
274+
text={(val) => (val === UNSET_VALUE ? "unset" : val)}
283275
className={cn(dimmed && "opacity-50")}
284276
>
285277
{(items) =>
286278
items.map((item) => (
287279
<SelectItem key={item} value={item}>
288-
{item === "__unset__" ? "unset" : item}
280+
{item === UNSET_VALUE ? "unset" : item}
289281
</SelectItem>
290282
))
291283
}

apps/webapp/app/models/admin.server.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,6 @@ export async function adminGetOrganizations(userId: string, { page, search }: Se
133133
v2Enabled: true,
134134
v3Enabled: true,
135135
deletedAt: true,
136-
featureFlags: true,
137136
members: {
138137
select: {
139138
user: {

apps/webapp/app/routes/admin.api.orgs.$orgId.feature-flags.ts

Lines changed: 33 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,18 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
2222

2323
const { orgId } = ParamsSchema.parse(params);
2424

25-
const organization = await prisma.organization.findUnique({
26-
where: { id: orgId },
27-
select: {
28-
id: true,
29-
title: true,
30-
slug: true,
31-
featureFlags: true,
32-
},
33-
});
25+
const [organization, globalFlags] = await Promise.all([
26+
prisma.organization.findUnique({
27+
where: { id: orgId },
28+
select: {
29+
id: true,
30+
title: true,
31+
slug: true,
32+
featureFlags: true,
33+
},
34+
}),
35+
getGlobalFlags(),
36+
]);
3437

3538
if (!organization) {
3639
throw new Response("Organization not found", { status: 404 });
@@ -41,7 +44,6 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
4144
: ({ success: false } as const);
4245

4346
const orgFlags = orgFlagsResult.success ? orgFlagsResult.data : {};
44-
const globalFlags = await getGlobalFlags();
4547
const controlTypes = getAllFlagControlTypes();
4648

4749
return json({
@@ -63,39 +65,33 @@ export async function action({ request, params }: ActionFunctionArgs) {
6365
}
6466

6567
const { orgId } = ParamsSchema.parse(params);
66-
67-
const organization = await prisma.organization.findUnique({
68-
where: { id: orgId },
69-
select: { id: true },
70-
});
71-
72-
if (!organization) {
73-
throw new Response("Organization not found", { status: 404 });
74-
}
75-
7668
const body = await request.json();
7769

78-
// body is the full overrides object (or null to clear all)
79-
if (body === null || (typeof body === "object" && Object.keys(body).length === 0)) {
70+
const featureFlags =
71+
body === null || (typeof body === "object" && Object.keys(body).length === 0)
72+
? Prisma.JsonNull
73+
: (() => {
74+
const validationResult = validatePartialFeatureFlags(body as Record<string, unknown>);
75+
if (!validationResult.success) {
76+
throw json(
77+
{ error: "Invalid feature flags", details: validationResult.error.issues },
78+
{ status: 400 }
79+
);
80+
}
81+
return validationResult.data;
82+
})();
83+
84+
try {
8085
await prisma.organization.update({
8186
where: { id: orgId },
82-
data: { featureFlags: Prisma.JsonNull },
87+
data: { featureFlags },
8388
});
84-
return json({ success: true });
89+
} catch (e) {
90+
if (e instanceof Prisma.PrismaClientKnownRequestError && e.code === "P2025") {
91+
throw new Response("Organization not found", { status: 404 });
92+
}
93+
throw e;
8594
}
8695

87-
const validationResult = validatePartialFeatureFlags(body as Record<string, unknown>);
88-
if (!validationResult.success) {
89-
return json(
90-
{ error: "Invalid feature flags", details: validationResult.error.issues },
91-
{ status: 400 }
92-
);
93-
}
94-
95-
await prisma.organization.update({
96-
where: { id: orgId },
97-
data: { featureFlags: validationResult.data },
98-
});
99-
10096
return json({ success: true });
10197
}

apps/webapp/app/routes/admin.orgs.tsx

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,11 @@ export default function AdminDashboardRoute() {
4949
const { organizations, filters, page, pageCount } = useTypedLoaderData<typeof loader>();
5050

5151
const [flagsOrgId, setFlagsOrgId] = useState<string | null>(null);
52-
const [flagsOrgTitle, setFlagsOrgTitle] = useState("");
5352
const [flagsOpen, setFlagsOpen] = useState(false);
53+
const flagsOrgTitle = organizations.find((o) => o.id === flagsOrgId)?.title ?? "";
5454

55-
const openFlagsDialog = (orgId: string, orgTitle: string) => {
55+
const openFlagsDialog = (orgId: string) => {
5656
setFlagsOrgId(orgId);
57-
setFlagsOrgTitle(orgTitle);
5857
setFlagsOpen(true);
5958
};
6059

@@ -126,23 +125,23 @@ export default function AdminDashboardRoute() {
126125
<TableCell>{org.deletedAt ? "☠️" : ""}</TableCell>
127126
<TableCell isSticky={true}>
128127
<div className="flex items-center gap-2">
129-
<Button
130-
variant="tertiary/small"
131-
onClick={() => openFlagsDialog(org.id, org.title)}
132-
>
133-
Flags
134-
</Button>
135-
<LinkButton
136-
to={`/@/orgs/${org.slug}`}
137-
variant="tertiary/small"
138-
shortcut={
139-
organizations.length === 1
140-
? { modifiers: ["mod"], key: "enter", enabledOnInputElements: true }
141-
: undefined
142-
}
143-
>
144-
Impersonate
145-
</LinkButton>
128+
<Button
129+
variant="tertiary/small"
130+
onClick={() => openFlagsDialog(org.id)}
131+
>
132+
Flags
133+
</Button>
134+
<LinkButton
135+
to={`/@/orgs/${org.slug}`}
136+
variant="tertiary/small"
137+
shortcut={
138+
organizations.length === 1
139+
? { modifiers: ["mod"], key: "enter", enabledOnInputElements: true }
140+
: undefined
141+
}
142+
>
143+
Impersonate
144+
</LinkButton>
146145
</div>
147146
</TableCell>
148147
</TableRow>

0 commit comments

Comments
 (0)