Conversation
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 3 potential issues.
Autofix Details
Bugbot Autofix prepared fixes for all 3 issues found in the latest run.
- ✅ Fixed: Empty state hides dimension selector, blocking valid comparisons
- Extracted the dimension selector into a shared JSX variable rendered in both the empty state and the normal comparison view, so users can always switch dimensions.
- ✅ Fixed: Quality fetch failure blanks entire FinOps overview tab
- Removed
qualityfrom the loading and data guard conditions so the tab renders with cost/productivity data regardless of the quality query state, passingquality.data?.attribution ?? []to the compare card.
- Removed
- ✅ Fixed: Duplicated
titleizeutility across three component files- Added a shared
titleize()export to@/lib/utilsand replaced all 4 duplicate local definitions with imports from the shared utility.
- Added a shared
Or push these changes by commenting:
@cursor push 2722a5f987
Preview (2722a5f987)
diff --git a/frontend/src/components/bottlenecks/root-cause-cluster-list.tsx b/frontend/src/components/bottlenecks/root-cause-cluster-list.tsx
--- a/frontend/src/components/bottlenecks/root-cause-cluster-list.tsx
+++ b/frontend/src/components/bottlenecks/root-cause-cluster-list.tsx
@@ -1,16 +1,12 @@
import { Badge } from "@/components/ui/badge"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
-import { formatPercent } from "@/lib/utils"
+import { formatPercent, titleize } from "@/lib/utils"
import type { RootCauseCluster } from "@/types/api"
interface RootCauseClusterListProps {
data: RootCauseCluster[]
}
-function titleize(value: string) {
- return value.replaceAll("_", " ")
-}
-
export function RootCauseClusterList({ data }: RootCauseClusterListProps) {
if (data.length === 0) return null
diff --git a/frontend/src/components/finops/overview-tab.tsx b/frontend/src/components/finops/overview-tab.tsx
--- a/frontend/src/components/finops/overview-tab.tsx
+++ b/frontend/src/components/finops/overview-tab.tsx
@@ -17,7 +17,7 @@
const productivity = useProductivity(teamId, startDate, endDate)
const quality = useQualityMetrics(teamId, startDate, endDate)
- if (costs.isLoading || productivity.isLoading || quality.isLoading) {
+ if (costs.isLoading || productivity.isLoading) {
return (
<div className="space-y-4">
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
@@ -32,7 +32,7 @@
)
}
- if (!costs.data || !productivity.data || !quality.data) return null
+ if (!costs.data || !productivity.data) return null
const costData = costs.data
const prod = productivity.data
@@ -85,7 +85,7 @@
<WorkflowCompareCard
workflowCosts={costData.workflow_breakdown}
- qualityRows={quality.data.attribution}
+ qualityRows={quality.data?.attribution ?? []}
/>
</div>
)
diff --git a/frontend/src/components/finops/workflow-compare-card.tsx b/frontend/src/components/finops/workflow-compare-card.tsx
--- a/frontend/src/components/finops/workflow-compare-card.tsx
+++ b/frontend/src/components/finops/workflow-compare-card.tsx
@@ -40,13 +40,28 @@
? rightLabel
: (labels.find((label) => label !== effectiveLeftLabel) ?? "")
+ const dimensionSelector = (
+ <label className="space-y-1">
+ <span className="text-xs font-medium text-muted-foreground">Dimension</span>
+ <select
+ value={dimension}
+ onChange={(e) => setDimension(e.target.value as CompareDimension)}
+ className="h-9 w-full rounded-md border border-input bg-background px-3 text-sm"
+ >
+ <option value="workflow_archetype">{DIMENSION_LABELS.workflow_archetype}</option>
+ <option value="workflow_fingerprint">{DIMENSION_LABELS.workflow_fingerprint}</option>
+ </select>
+ </label>
+ )
+
if (options.length < 2) {
return (
<Card>
<CardHeader>
<CardTitle>Workflow Compare</CardTitle>
</CardHeader>
- <CardContent>
+ <CardContent className="space-y-4">
+ <div className="max-w-xs">{dimensionSelector}</div>
<p className="text-sm text-muted-foreground">
Need at least two workflows with cost data to compare them.
</p>
@@ -82,17 +97,7 @@
</CardHeader>
<CardContent className="space-y-4">
<div className="grid gap-4 md:grid-cols-3">
- <label className="space-y-1">
- <span className="text-xs font-medium text-muted-foreground">Dimension</span>
- <select
- value={dimension}
- onChange={(e) => setDimension(e.target.value as CompareDimension)}
- className="h-9 w-full rounded-md border border-input bg-background px-3 text-sm"
- >
- <option value="workflow_archetype">{DIMENSION_LABELS.workflow_archetype}</option>
- <option value="workflow_fingerprint">{DIMENSION_LABELS.workflow_fingerprint}</option>
- </select>
- </label>
+ {dimensionSelector}
<label className="space-y-1">
<span className="text-xs font-medium text-muted-foreground">Workflow A</span>
diff --git a/frontend/src/components/growth/bright-spot-cards.tsx b/frontend/src/components/growth/bright-spot-cards.tsx
--- a/frontend/src/components/growth/bright-spot-cards.tsx
+++ b/frontend/src/components/growth/bright-spot-cards.tsx
@@ -2,17 +2,13 @@
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Badge } from "@/components/ui/badge"
-import { formatDuration, formatPercent } from "@/lib/utils"
+import { formatDuration, formatPercent, titleize } from "@/lib/utils"
import type { BrightSpot } from "@/types/api"
interface BrightSpotCardsProps {
spots: BrightSpot[]
}
-function titleize(value: string) {
- return value.replaceAll("_", " ")
-}
-
export function BrightSpotCards({ spots }: BrightSpotCardsProps) {
if (spots.length === 0) {
return (
diff --git a/frontend/src/components/growth/workflow-playbook-cards.tsx b/frontend/src/components/growth/workflow-playbook-cards.tsx
--- a/frontend/src/components/growth/workflow-playbook-cards.tsx
+++ b/frontend/src/components/growth/workflow-playbook-cards.tsx
@@ -1,6 +1,6 @@
import { Badge } from "@/components/ui/badge"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
-import { formatDuration, formatPercent } from "@/lib/utils"
+import { formatDuration, formatPercent, titleize } from "@/lib/utils"
import type { WorkflowPlaybook } from "@/types/api"
interface WorkflowPlaybookCardsProps {
@@ -13,10 +13,6 @@
already_using: "outline",
}
-function titleize(value: string) {
- return value.replaceAll("_", " ")
-}
-
export function WorkflowPlaybookCards({ playbooks }: WorkflowPlaybookCardsProps) {
if (playbooks.length === 0) {
return (
diff --git a/frontend/src/components/projects/project-workflow-section.tsx b/frontend/src/components/projects/project-workflow-section.tsx
--- a/frontend/src/components/projects/project-workflow-section.tsx
+++ b/frontend/src/components/projects/project-workflow-section.tsx
@@ -1,16 +1,12 @@
import { Badge } from "@/components/ui/badge"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
-import { formatDuration, formatNumber, formatPercent } from "@/lib/utils"
+import { formatDuration, formatNumber, formatPercent, titleize } from "@/lib/utils"
import type { ProjectWorkflowSummary } from "@/types/api"
interface ProjectWorkflowSectionProps {
workflowSummary: ProjectWorkflowSummary
}
-function titleize(value: string) {
- return value.replaceAll("_", " ")
-}
-
export function ProjectWorkflowSection({ workflowSummary }: ProjectWorkflowSectionProps) {
return (
<div className="grid gap-6 xl:grid-cols-[minmax(0,1fr)_minmax(0,1fr)]">
diff --git a/frontend/src/lib/utils.ts b/frontend/src/lib/utils.ts
--- a/frontend/src/lib/utils.ts
+++ b/frontend/src/lib/utils.ts
@@ -27,6 +27,10 @@
return `$${dollars.toFixed(2)}`
}
+export function titleize(value: string): string {
+ return value.replaceAll("_", " ")
+}
+
export function formatLabel(value: string): string {
return value
.replaceAll("_", " ")04bbdfe to
bce1585
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
There are 4 total unresolved issues (including 3 from previous reviews).
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Duplicate
formatMetricutility across multiple components- Centralized
formatMetricintoutils.tsand updated all three component files to import from the single source, eliminating the duplicated null-safe numeric formatting logic.
- Centralized
Or push these changes by commenting:
@cursor push bb3d80a44b
Preview (bb3d80a44b)
diff --git a/frontend/src/components/finops/workflow-compare-card.tsx b/frontend/src/components/finops/workflow-compare-card.tsx
--- a/frontend/src/components/finops/workflow-compare-card.tsx
+++ b/frontend/src/components/finops/workflow-compare-card.tsx
@@ -1,7 +1,7 @@
import { useState } from "react"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
-import { formatCost, formatLabel, formatPercent } from "@/lib/utils"
+import { formatCost, formatLabel, formatMetric, formatPercent } from "@/lib/utils"
import type { QualityAttributionRow, WorkflowCostBreakdown } from "@/types/api"
type CompareDimension = "workflow_archetype" | "workflow_fingerprint"
@@ -20,10 +20,6 @@
return dimension === "workflow_archetype" ? formatLabel(value) : value
}
-function formatMetric(value: number | null | undefined, digits = 1): string {
- return value == null ? "-" : value.toFixed(digits)
-}
-
export function WorkflowCompareCard({
workflowCosts,
qualityRows,
diff --git a/frontend/src/components/quality/claude-pr-comparison.tsx b/frontend/src/components/quality/claude-pr-comparison.tsx
--- a/frontend/src/components/quality/claude-pr-comparison.tsx
+++ b/frontend/src/components/quality/claude-pr-comparison.tsx
@@ -1,7 +1,7 @@
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { CardSkeleton } from "@/components/shared/loading-skeleton"
import { useClaudePRComparison } from "@/hooks/use-api-queries"
-import { cn } from "@/lib/utils"
+import { cn, formatMetric } from "@/lib/utils"
import { GitPullRequest } from "lucide-react"
import type { PRGroupMetrics } from "@/types/api"
@@ -11,9 +11,9 @@
endDate?: string
}
-function formatMetric(value: number | null, suffix = ""): string {
- if (value == null) return "-"
- return `${value.toFixed(1)}${suffix}`
+function formatMetricWithSuffix(value: number | null, suffix = ""): string {
+ const base = formatMetric(value)
+ return base === "-" ? base : `${base}${suffix}`
}
function DeltaIndicator({ value }: { value: number | null }) {
@@ -56,8 +56,8 @@
return (
<div className="grid grid-cols-4 gap-4 border-b border-border py-3 last:border-0">
<div className="text-sm text-muted-foreground">{label}</div>
- <div className="text-sm font-medium">{formatMetric(claudeValue, suffix)}</div>
- <div className="text-sm font-medium">{formatMetric(otherValue, suffix)}</div>
+ <div className="text-sm font-medium">{formatMetricWithSuffix(claudeValue, suffix)}</div>
+ <div className="text-sm font-medium">{formatMetricWithSuffix(otherValue, suffix)}</div>
<div className="flex items-center">
<DeltaIndicator value={displayDelta ?? null} />
</div>
diff --git a/frontend/src/components/quality/quality-attribution-table.tsx b/frontend/src/components/quality/quality-attribution-table.tsx
--- a/frontend/src/components/quality/quality-attribution-table.tsx
+++ b/frontend/src/components/quality/quality-attribution-table.tsx
@@ -1,4 +1,4 @@
-import { formatLabel } from "@/lib/utils"
+import { formatLabel, formatMetric } from "@/lib/utils"
import type { QualityAttributionRow } from "@/types/api"
interface Props {
@@ -18,10 +18,6 @@
return value == null ? "-" : `${(value * 100).toFixed(0)}%`
}
-function formatMetric(value: number | null, digits = 1): string {
- return value == null ? "-" : value.toFixed(digits)
-}
-
export function QualityAttributionTable({ rows }: Props) {
if (rows.length === 0) {
return (
diff --git a/frontend/src/lib/utils.ts b/frontend/src/lib/utils.ts
--- a/frontend/src/lib/utils.ts
+++ b/frontend/src/lib/utils.ts
@@ -33,6 +33,10 @@
.replace(/\b\w/g, (char) => char.toUpperCase())
}
+export function formatMetric(value: number | null | undefined, digits = 1): string {
+ return value == null ? "-" : value.toFixed(digits)
+}
+
export function formatPercent(value: number | null | undefined): string {
if (value == null) return "-"
return `${(value * 100).toFixed(0)}%`
Owner
Author
|
Could not push Autofix changes. The PR branch may have changed since the Autofix ran, or the Autofix commit may no longer exist. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Summary
Validation
PYTHONPATH=/Users/ccf/git/primer/src pytest tests/test_analytics.py -qcd frontend && /Users/ccf/git/primer/frontend/node_modules/.bin/vitest run --run src/components/finops/__tests__/workflow-cost-table.test.tsx src/components/finops/__tests__/workflow-compare-card.test.tsxNote
Medium Risk
Adds new comparison UI that joins cost and quality datasets and introduces a new
useQualityMetricsfetch in the FinOps overview, which could affect loading/empty-state behavior and displayed metrics.Overview
Adds a new Workflow Compare card to the FinOps Overview, letting users pick a dimension (
workflow_archetypeorworkflow_fingerprint) and compare two workflows side-by-side across spend, cost-efficiency, and quality outcomes.Introduces shared formatting helpers in
lib/utils(titleize,formatMetric) and refactors several components to use them, plus a newWorkflowCompareCardtest covering empty-state and dimension switching.Written by Cursor Bugbot for commit 142689f. This will update automatically on new commits. Configure here.