diff --git a/web/src/modules/swiss/components/ReleaseObjects.tsx b/web/src/modules/swiss/components/ReleaseObjects.tsx
index 6bfa9e2..c3b2b5f 100644
--- a/web/src/modules/swiss/components/ReleaseObjects.tsx
+++ b/web/src/modules/swiss/components/ReleaseObjects.tsx
@@ -246,7 +246,7 @@ function ScalerPanel({
);
const current = status?.currentReplicas;
const desired = status?.desiredReplicas;
- const failing = status?.conditions?.find((c) => c.status === "False");
+ const failing = scalerFailing(status?.conditions);
const custom = spec?.metricProvider === "Custom";
let state: React.ReactNode = null;
@@ -349,7 +349,7 @@ function ScalerPanel({
)}
-
+
>
)}
@@ -681,7 +681,18 @@ function Stale({
);
}
-export function Conditions({ conditions }: { conditions?: Condition[] }) {
+// Only AbleToScale=False or ScalingActive=False (DecisionAvailable on operator 0.5.0) is a scaler failure.
+export function scalerFailing(conditions?: Condition[]): Condition | undefined {
+ const of = (type: string) => conditions?.find((c) => c.type === type);
+ return [of("AbleToScale"), of("ScalingActive") ?? of("DecisionAvailable")].find((c) => c?.status === "False");
+}
+
+// ScalingLimited is informational; DecisionAvailable only mirrors ScalingActive when that is present.
+function scalerNeutral(conditions?: Condition[]): string[] {
+ return conditions?.some((c) => c.type === "ScalingActive") ? ["ScalingLimited", "DecisionAvailable"] : ["ScalingLimited"];
+}
+
+export function Conditions({ conditions, neutral = [] }: { conditions?: Condition[]; neutral?: string[] }) {
if (!conditions?.length) return null;
return (
@@ -691,11 +702,13 @@ export function Conditions({ conditions }: { conditions?: Condition[] }) {
{c.type}
diff --git a/web/src/modules/swiss/components/scaler-conditions.test.ts b/web/src/modules/swiss/components/scaler-conditions.test.ts
new file mode 100644
index 0000000..a9b1889
--- /dev/null
+++ b/web/src/modules/swiss/components/scaler-conditions.test.ts
@@ -0,0 +1,23 @@
+import { describe, expect, it } from "vitest";
+import { scalerFailing } from "@swiss/components/ReleaseObjects";
+
+describe("scalerFailing", () => {
+ it("does not treat ScalingLimited as a failure", () => {
+ expect(scalerFailing([
+ { type: "ScalingActive", status: "True", reason: "DecisionReceived" },
+ { type: "AbleToScale", status: "True", reason: "Synced" },
+ { type: "ScalingLimited", status: "False", reason: "DesiredWithinRange" },
+ ])).toBeUndefined();
+ });
+ it("fails on AbleToScale=False or ScalingActive=False", () => {
+ expect(scalerFailing([{ type: "AbleToScale", status: "False", reason: "TargetNotFound" }])?.reason).toBe("TargetNotFound");
+ expect(scalerFailing([{ type: "ScalingActive", status: "False", reason: "InvalidSpec" }])?.reason).toBe("InvalidSpec");
+ });
+ it("uses DecisionAvailable only when ScalingActive is absent", () => {
+ expect(scalerFailing([{ type: "DecisionAvailable", status: "False", reason: "DecisionUnavailable" }])?.reason).toBe("DecisionUnavailable");
+ expect(scalerFailing([
+ { type: "ScalingActive", status: "True", reason: "MetricsAvailable" },
+ { type: "DecisionAvailable", status: "False", reason: "DecisionUnavailable" },
+ ])).toBeUndefined();
+ });
+});
diff --git a/web/src/modules/swiss/routes/DeploymentDetail.tsx b/web/src/modules/swiss/routes/DeploymentDetail.tsx
index 23c6f9e..1fd25cc 100644
--- a/web/src/modules/swiss/routes/DeploymentDetail.tsx
+++ b/web/src/modules/swiss/routes/DeploymentDetail.tsx
@@ -32,7 +32,7 @@ import { Dialog } from "@swiss/components/ui/dialog";
import { Field, Input } from "@swiss/components/ui/input";
import { CatalogModelDiff } from "@swiss/components/CatalogModelDiff";
import { Provenance } from "@swiss/components/Provenance";
-import { ReleaseObjects, pick, useReleaseObjects } from "@swiss/components/ReleaseObjects";
+import { ReleaseObjects, pick, scalerFailing, useReleaseObjects } from "@swiss/components/ReleaseObjects";
import { AccessPoint } from "@swiss/components/Endpoint";
import { ReleaseStatus } from "@swiss/components/ReleaseStatus";
import { SLODialog } from "@swiss/components/SLOCard";
@@ -342,7 +342,7 @@ function scalerStep({ result, status }: ReturnType c.status === "False");
+ const failing = scalerFailing(status?.conditions);
if (failing) return { label, state: "bad", detail: failing.reason || failing.type };
const cur = status?.currentReplicas;
const want = status?.desiredReplicas;