From 41219bb22ad8c9bc0517f69e7762940c3c03aebb Mon Sep 17 00:00:00 2001 From: TerrifiedBug Date: Mon, 9 Mar 2026 13:51:28 +0000 Subject: [PATCH 1/2] =?UTF-8?q?fix:=20resolve=20post-merge=20settings=20bu?= =?UTF-8?q?gs=20=E2=80=94=20SCIM=20duplication,=20team=20settings=20crash,?= =?UTF-8?q?=20error=20boundary=20navigation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove inline ScimSettings from auth-settings page (now has dedicated /settings/scim route) - Add throwOnError/retry guards to settings.get query in team-settings (requires super-admin, crashes for team admins) - Add componentDidUpdate to ErrorBoundary to reset error state on navigation --- src/app/(dashboard)/settings/_components/auth-settings.tsx | 3 --- src/app/(dashboard)/settings/_components/team-settings.tsx | 7 ++++++- src/components/error-boundary.tsx | 7 +++++++ 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/app/(dashboard)/settings/_components/auth-settings.tsx b/src/app/(dashboard)/settings/_components/auth-settings.tsx index cb908562..10ebaaee 100644 --- a/src/app/(dashboard)/settings/_components/auth-settings.tsx +++ b/src/app/(dashboard)/settings/_components/auth-settings.tsx @@ -47,7 +47,6 @@ import { PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; -import { ScimSettings } from "./scim-settings"; // ─── Auth Tab ────────────────────────────────────────────────────────────────── @@ -603,8 +602,6 @@ export function AuthSettings() { - - ); } diff --git a/src/app/(dashboard)/settings/_components/team-settings.tsx b/src/app/(dashboard)/settings/_components/team-settings.tsx index b379ddbb..acffb688 100644 --- a/src/app/(dashboard)/settings/_components/team-settings.tsx +++ b/src/app/(dashboard)/settings/_components/team-settings.tsx @@ -120,7 +120,12 @@ export function TeamSettings() { }) ); - const settingsQuery = useQuery(trpc.settings.get.queryOptions()); + // settings.get requires super-admin — silently degrade for team admins + const settingsQuery = useQuery({ + ...trpc.settings.get.queryOptions(), + retry: false, + throwOnError: false, + }); const oidcConfigured = !!(settingsQuery.data?.oidcIssuer && settingsQuery.data?.oidcClientId); const [resetPasswordOpen, setResetPasswordOpen] = useState(false); diff --git a/src/components/error-boundary.tsx b/src/components/error-boundary.tsx index 1490f130..51600e98 100644 --- a/src/components/error-boundary.tsx +++ b/src/components/error-boundary.tsx @@ -24,6 +24,13 @@ export class ErrorBoundary extends Component { return { hasError: true, error }; } + componentDidUpdate(prevProps: Props) { + // Reset error state when children change (e.g. route navigation) + if (this.state.hasError && prevProps.children !== this.props.children) { + this.setState({ hasError: false, error: undefined }); + } + } + render() { if (this.state.hasError) { return ( From 6606bfe16db4248d5dc7e5aa40b229a0f59f5e05 Mon Sep 17 00:00:00 2001 From: TerrifiedBug Date: Mon, 9 Mar 2026 13:56:14 +0000 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20alert=20UI=20polish=20=E2=80=94=20hi?= =?UTF-8?q?de=20threshold=20for=20binary=20metrics,=20hide=20pipeline=20fo?= =?UTF-8?q?r=20global=20metrics?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Show "—" for condition, threshold, and duration on binary metrics (pipeline_crashed, node_unreachable) in the rules table - Hide pipeline dropdown for metrics that aren't pipeline-scopable (node_unreachable, new_version_available, scim_sync_failed, backup_failed, certificate_expiring, node_joined, node_left) - Move metric selector before pipeline dropdown so the pipeline field reacts to metric selection - Clear pipelineId when switching to a global metric - Show "Notifications will be sent when this event occurs" for binary metrics in the form --- src/app/(dashboard)/alerts/page.tsx | 121 ++++++++++++++++------------ 1 file changed, 69 insertions(+), 52 deletions(-) diff --git a/src/app/(dashboard)/alerts/page.tsx b/src/app/(dashboard)/alerts/page.tsx index 725105d0..b11a583f 100644 --- a/src/app/(dashboard)/alerts/page.tsx +++ b/src/app/(dashboard)/alerts/page.tsx @@ -95,6 +95,17 @@ const CONDITION_LABELS: Record = { const BINARY_METRICS = new Set(["node_unreachable", "pipeline_crashed"]); +/** Metrics that cannot be scoped to a specific pipeline. */ +const GLOBAL_METRICS = new Set([ + "node_unreachable", + "new_version_available", + "scim_sync_failed", + "backup_failed", + "certificate_expiring", + "node_joined", + "node_left", +]); + const CHANNEL_TYPE_LABELS: Record = { slack: "Slack", email: "Email", @@ -234,14 +245,14 @@ function AlertRulesSection({ environmentId }: { environmentId: string }) { const openEdit = (rule: (typeof rules)[0]) => { setEditingRuleId(rule.id); - const isEvent = isEventMetric(rule.metric); + const skipThreshold = isEventMetric(rule.metric) || BINARY_METRICS.has(rule.metric); setForm({ name: rule.name, pipelineId: rule.pipelineId ?? "", metric: rule.metric, - condition: isEvent ? "" : (rule.condition ?? "gt"), - threshold: isEvent ? "" : String(rule.threshold ?? ""), - durationSeconds: isEvent ? "" : String(rule.durationSeconds ?? ""), + condition: skipThreshold ? "" : (rule.condition ?? "gt"), + threshold: skipThreshold ? "" : String(rule.threshold ?? ""), + durationSeconds: skipThreshold ? "" : String(rule.durationSeconds ?? ""), channelIds: rule.channels?.map((c) => c.channelId) ?? [], }); setDialogOpen(true); @@ -264,11 +275,13 @@ function AlertRulesSection({ environmentId }: { environmentId: string }) { return; } + const skipThreshold = isEvent || isBinary; + if (editingRuleId) { updateMutation.mutate({ id: editingRuleId, name: form.name, - ...(isEvent + ...(skipThreshold ? {} : { threshold: parseFloat(form.threshold), @@ -282,9 +295,9 @@ function AlertRulesSection({ environmentId }: { environmentId: string }) { environmentId, pipelineId: form.pipelineId || undefined, metric: form.metric as AlertMetric, - condition: isEvent ? null : (form.condition as AlertCondition), - threshold: isEvent ? null : parseFloat(form.threshold), - durationSeconds: isEvent ? null : (parseInt(form.durationSeconds, 10) || 60), + condition: skipThreshold ? null : (form.condition as AlertCondition), + threshold: skipThreshold ? null : parseFloat(form.threshold), + durationSeconds: skipThreshold ? null : (parseInt(form.durationSeconds, 10) || 60), teamId: selectedTeamId!, channelIds: form.channelIds.length > 0 ? form.channelIds : undefined, }); @@ -342,14 +355,18 @@ function AlertRulesSection({ environmentId }: { environmentId: string }) { - {rule.condition ? (CONDITION_LABELS[rule.condition] ?? rule.condition) : "—"} + {BINARY_METRICS.has(rule.metric) || !rule.condition ? "—" : (CONDITION_LABELS[rule.condition] ?? rule.condition)} + + + {BINARY_METRICS.has(rule.metric) ? "—" : (rule.threshold ?? "—")} - {rule.threshold ?? "—"} - {rule.durationSeconds != null ? `${rule.durationSeconds}s` : "—"} + {BINARY_METRICS.has(rule.metric) || rule.durationSeconds == null ? "—" : `${rule.durationSeconds}s`} - {rule.pipeline ? ( + {GLOBAL_METRICS.has(rule.metric) ? ( + + ) : rule.pipeline ? ( {rule.pipeline.name} ) : ( All @@ -429,31 +446,6 @@ function AlertRulesSection({ environmentId }: { environmentId: string }) { {!editingRuleId && ( <> -
- - -
-
+ {!GLOBAL_METRICS.has(form.metric) && ( +
+ + +
+ )} )} - {isEventMetric(form.metric) ? ( + {isEventMetric(form.metric) || BINARY_METRICS.has(form.metric) ? (

Notifications will be sent when this event occurs.

) : ( <> - {!BINARY_METRICS.has(form.metric) && ( -
- - - setForm((f) => ({ ...f, threshold: e.target.value })) - } - /> -
- )} +
+ + + setForm((f) => ({ ...f, threshold: e.target.value })) + } + /> +