import { FeatureFlagsEditor, type FeatureFlagMetadata, type SetFeatureFlagInput, } from "@agent-native/core/client/feature-flags"; import { useActionMutation, useActionQuery, } from "@agent-native/core/client/hooks"; import { useT } from "@agent-native/core/client/i18n"; import { IconRefresh } from "@tabler/icons-react"; import { useQueryClient } from "@tanstack/react-query"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; export interface FeatureFlag { key: string; displayName?: string | null; description?: string | null; defaultValue?: boolean; rules?: FeatureFlagMetadata["rules"]; } export interface FlagApp { appId?: string; appName?: string; appOrigin?: string; state?: string; id?: string; name?: string; url?: string; status?: string; reason?: string; flags?: FeatureFlag[]; } export interface FlagDirectory { directoryStatus?: string; apps?: FlagApp[]; } function asDirectory(value: unknown): FlagDirectory { return value && typeof value === "object" ? (value as FlagDirectory) : {}; } function isReady(app: FlagApp) { return app.state === "ready" || app.status === "ready"; } function statusLabel(app: FlagApp) { return app.state || app.status || "unknown-legacy"; } function appId(app: FlagApp) { return app.appId || app.id || ""; } function appName(app: FlagApp) { return app.appName || app.name || appId(app); } export function qualifyFleetMutation( appId: string, input: SetFeatureFlagInput, ) { return { appId, ...input }; } export function FeatureFlagsFleetPanel() { const t = useT(); const client = useQueryClient(); const flags = useActionQuery( "list-workspace-feature-flags", undefined, { retry: false }, ); const mutation = useActionMutation< unknown, { appId: string } & SetFeatureFlagInput >("set-workspace-feature-flag", { onMutate: async (input) => { await client.cancelQueries({ queryKey: ["action", "list-workspace-feature-flags"], }); const key = ["action", "list-workspace-feature-flags", undefined]; const previous = client.getQueryData(key); client.setQueryData(key, (old: unknown) => { const data = asDirectory(old); return { ...data, apps: data.apps?.map((app) => appId(app) !== input.appId ? app : { ...app, flags: app.flags?.map((flag) => flag.key !== input.key ? flag : { ...flag, rules: input.operation === "replace-rules" && input.rules ? input.rules : input.operation === "off" ? { ...flag.rules!, mode: "off", emails: [], orgIds: [], percentage: 0, } : flag.rules, }, ), }, ), }; }); return { previous, key }; }, onError: (_error, _input, context: any) => { if (context) client.setQueryData(context.key, context.previous); }, onSettled: () => void client.invalidateQueries({ queryKey: ["action", "list-workspace-feature-flags"], }), }); const directory = asDirectory(flags.data); const apps = directory.apps ?? []; if (flags.isLoading) return ; if (flags.error || directory.directoryStatus === "unavailable") return ( void flags.refetch()} /> ); if (apps.length === 0) return ( void flags.refetch()} /> ); return (

{t("agents.featureFlags")}

{t("agents.featureFlagsDescription")}

{apps.map((app) => (

{appName(app)}

{app.reason ? (

{app.reason}

) : null}
{!isReady(app) ? ( {statusLabel(app)} ) : null}
{isReady(app) ? (
!!flag.rules && typeof flag.defaultValue === "boolean", )} isPending={mutation.isPending} error={ mutation.variables?.appId === appId(app) ? mutation.error : null } errorFlagKey={ mutation.variables?.appId === appId(app) ? mutation.variables.key : null } showHeader={false} onMutate={(input) => mutation.mutate(qualifyFleetMutation(appId(app), input)) } />
) : (
{statusLabel(app) === "no-definitions" ? t("agents.noFlagDefinitions") : t("agents.flagsNotReady", { status: statusLabel(app) })}
)}
))}
); } function PanelLoading() { const t = useT(); return (
{t("agents.loading")}
); } function StatusState({ title, detail, onRetry, }: { title: string; detail: string; onRetry: () => void; }) { const t = useT(); return (

{title}

{detail}

); }