Skip to content

Commit 0d34e8b

Browse files
[dev] [Marfuen] mariano/trigger-errors-3 (#1731)
* refactor: dont crash trigger on cloud tests job * chore: fix types * chore: add forced fail prop for testing * chore: fix types * refactor(cloud-tests): integrate trigger token creation and update session handling * refactor(cloud-tests): remove debug console logs from TestsLayout * chore: remove leftover code --------- Co-authored-by: Mariano Fuentes <marfuen98@gmail.com>
1 parent 16d1cba commit 0d34e8b

5 files changed

Lines changed: 225 additions & 378 deletions

File tree

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
'use server';
2+
3+
import { auth as betterAuth } from '@/utils/auth';
4+
import { auth } from '@trigger.dev/sdk';
5+
import { headers } from 'next/headers';
6+
7+
export const createTriggerToken = async () => {
8+
const session = await betterAuth.api.getSession({
9+
headers: await headers(),
10+
});
11+
12+
if (!session) {
13+
return {
14+
success: false,
15+
error: 'Unauthorized',
16+
};
17+
}
18+
19+
const orgId = session.session?.activeOrganizationId;
20+
if (!orgId) {
21+
return {
22+
success: false,
23+
error: 'No active organization',
24+
};
25+
}
26+
27+
try {
28+
const token = await auth.createTriggerPublicToken('run-integration-tests', {
29+
multipleUse: true,
30+
expirationTime: '1hr',
31+
});
32+
33+
return {
34+
success: true,
35+
token,
36+
};
37+
} catch (error) {
38+
console.error('Error creating trigger token:', error);
39+
return {
40+
success: false,
41+
error: error instanceof Error ? error.message : 'Failed to create trigger token',
42+
};
43+
}
44+
};

apps/app/src/app/(app)/[orgId]/cloud-tests/components/ResultsView.tsx

Lines changed: 56 additions & 155 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,9 @@
22

33
import { Button } from '@comp/ui/button';
44
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@comp/ui/select';
5-
import { useRealtimeRun } from '@trigger.dev/react-hooks';
5+
import type { AnyRealtimeRun } from '@trigger.dev/sdk';
66
import { AlertTriangle, CheckCircle2, Info, Loader2, RefreshCw, X } from 'lucide-react';
7-
import { useEffect, useState } from 'react';
8-
import { isFailureRunStatus, isSuccessfulRunStatus, isTerminalRunStatus } from '../status';
9-
import type { IntegrationRunOutput } from '../types';
7+
import { useMemo, useState } from 'react';
108
import { FindingsTable } from './FindingsTable';
119

1210
interface Finding {
@@ -21,127 +19,68 @@ interface Finding {
2119

2220
interface ResultsViewProps {
2321
findings: Finding[];
24-
scanTaskId: string | null;
25-
scanAccessToken: string | null;
2622
onRunScan: () => Promise<string | null>;
2723
isScanning: boolean;
28-
runOutput: IntegrationRunOutput | null;
24+
run: AnyRealtimeRun | undefined;
2925
}
3026

3127
const severityOrder = { critical: 0, high: 1, medium: 2, low: 3, info: 4 };
3228

33-
// Helper function to extract clean error messages from cloud provider errors
34-
function extractCleanErrorMessage(errorMessage: string): string {
35-
try {
36-
// Try to parse as JSON (GCP returns JSON blob)
37-
const parsed = JSON.parse(errorMessage);
38-
39-
// GCP error structure: { error: { message: "actual message" } }
40-
if (parsed.error?.message) {
41-
return parsed.error.message;
42-
}
43-
} catch {
44-
// Not JSON, return original
45-
}
46-
47-
return errorMessage;
48-
}
49-
50-
export function ResultsView({
51-
findings,
52-
scanTaskId,
53-
scanAccessToken,
54-
onRunScan,
55-
isScanning,
56-
runOutput,
57-
}: ResultsViewProps) {
58-
// Track scan status with Trigger.dev hooks
59-
const { run } = useRealtimeRun(scanTaskId || '', {
60-
enabled: !!scanTaskId && !!scanAccessToken,
61-
accessToken: scanAccessToken || undefined,
62-
});
63-
64-
const runStatus = run?.status;
29+
export function ResultsView({ findings, onRunScan, isScanning, run }: ResultsViewProps) {
6530
const [selectedStatus, setSelectedStatus] = useState<string>('all');
6631
const [selectedSeverity, setSelectedSeverity] = useState<string>('all');
67-
const [showSuccessBanner, setShowSuccessBanner] = useState(false);
68-
const [showErrorBanner, setShowErrorBanner] = useState(false);
69-
const [showOutputErrorBanner, setShowOutputErrorBanner] = useState(false);
70-
71-
const runOutputError = runOutput && !runOutput.success ? runOutput : null;
72-
const isRunTerminal = isTerminalRunStatus(runStatus);
73-
const scanSucceeded = isRunTerminal && !runOutputError && isSuccessfulRunStatus(runStatus);
74-
const runHasHardFailure = isRunTerminal && (isFailureRunStatus(runStatus) || Boolean(run?.error));
75-
const outputErrorMessages =
76-
runOutputError?.errors && runOutputError.errors.length > 0
77-
? runOutputError.errors
78-
: (runOutputError?.failedIntegrations?.map(
79-
(integration) => `${integration.name}: ${integration.error}`,
80-
) ?? []);
81-
82-
// Show success banner when scan completes successfully, auto-hide after 5 seconds
83-
useEffect(() => {
84-
if (scanSucceeded) {
85-
setShowSuccessBanner(true);
86-
const timer = setTimeout(() => {
87-
setShowSuccessBanner(false);
88-
}, 5000);
89-
return () => clearTimeout(timer);
90-
}
91-
setShowSuccessBanner(false);
92-
}, [scanSucceeded]);
93-
94-
// Auto-dismiss error banner after 30 seconds
95-
useEffect(() => {
96-
if (runHasHardFailure) {
97-
setShowErrorBanner(true);
98-
const timer = setTimeout(() => {
99-
setShowErrorBanner(false);
100-
}, 30000);
101-
return () => clearTimeout(timer);
102-
}
103-
setShowErrorBanner(false);
104-
}, [runHasHardFailure]);
10532

106-
// Show output error banner when run completes with errors
107-
useEffect(() => {
108-
if (runOutputError) {
109-
setShowOutputErrorBanner(true);
110-
setShowSuccessBanner(false);
111-
} else {
112-
setShowOutputErrorBanner(false);
113-
}
114-
}, [runOutputError]);
33+
const isCompleted = run?.status === 'COMPLETED';
34+
const isFailed =
35+
run?.status === 'FAILED' ||
36+
run?.status === 'CRASHED' ||
37+
run?.status === 'SYSTEM_FAILURE' ||
38+
run?.status === 'TIMED_OUT' ||
39+
run?.status === 'CANCELED';
40+
41+
const runOutput =
42+
run?.output && typeof run.output === 'object' && 'success' in run.output
43+
? (run.output as {
44+
success: boolean;
45+
errors?: string[];
46+
failedIntegrations?: Array<{ name: string; error: string }>;
47+
})
48+
: null;
49+
50+
const hasOutputErrors = runOutput && !runOutput.success;
51+
const outputErrorMessages = hasOutputErrors
52+
? (runOutput.errors ?? runOutput.failedIntegrations?.map((i) => `${i.name}: ${i.error}`) ?? [])
53+
: [];
11554

116-
// Get unique statuses and severities
11755
const uniqueStatuses = Array.from(
11856
new Set(findings.map((f) => f.status).filter(Boolean) as string[]),
11957
);
12058
const uniqueSeverities = Array.from(
12159
new Set(findings.map((f) => f.severity).filter(Boolean) as string[]),
12260
);
12361

124-
// Filter findings
12562
const filteredFindings = findings.filter((finding) => {
12663
const matchesStatus = selectedStatus === 'all' || finding.status === selectedStatus;
12764
const matchesSeverity = selectedSeverity === 'all' || finding.severity === selectedSeverity;
12865
return matchesStatus && matchesSeverity;
12966
});
13067

131-
// Sort findings by severity (always)
132-
const sortedFindings = [...filteredFindings].sort((a, b) => {
133-
const severityA = a.severity
134-
? (severityOrder[a.severity.toLowerCase() as keyof typeof severityOrder] ?? 999)
135-
: 999;
136-
const severityB = b.severity
137-
? (severityOrder[b.severity.toLowerCase() as keyof typeof severityOrder] ?? 999)
138-
: 999;
139-
return severityA - severityB;
140-
});
68+
const sortedFindings = useMemo(
69+
() =>
70+
[...filteredFindings].sort((a, b) => {
71+
const severityA = a.severity
72+
? (severityOrder[a.severity.toLowerCase() as keyof typeof severityOrder] ?? 999)
73+
: 999;
74+
const severityB = b.severity
75+
? (severityOrder[b.severity.toLowerCase() as keyof typeof severityOrder] ?? 999)
76+
: 999;
77+
return severityA - severityB;
78+
}),
79+
[filteredFindings],
80+
);
14181

14282
return (
14383
<div className="flex flex-col gap-6">
144-
{/* Scan Status Banner */}
14584
{isScanning && (
14685
<div className="bg-primary/10 flex items-center gap-3 rounded-lg border border-primary/20 p-4">
14786
<Loader2 className="text-primary h-5 w-5 animate-spin flex-shrink-0" />
@@ -154,99 +93,62 @@ export function ResultsView({
15493
</div>
15594
)}
15695

157-
{showSuccessBanner && scanSucceeded && !isScanning && (
96+
{isCompleted && !isScanning && !hasOutputErrors && (
15897
<div className="bg-primary/10 flex items-center gap-3 rounded-lg border border-primary/20 p-4">
15998
<CheckCircle2 className="text-primary h-5 w-5 flex-shrink-0" />
16099
<div className="flex-1">
161100
<p className="text-primary text-sm font-medium">Scan completed</p>
162101
<p className="text-muted-foreground text-xs">Results updated successfully</p>
163102
</div>
164-
<Button
165-
variant="ghost"
166-
size="sm"
167-
onClick={() => setShowSuccessBanner(false)}
168-
className="text-muted-foreground hover:text-foreground h-auto p-1"
169-
>
170-
<X className="h-4 w-4" />
171-
</Button>
172103
</div>
173104
)}
174105

175-
{/* Output error banner when run reports errors but job didn't crash */}
176-
{showOutputErrorBanner && runOutputError && !isScanning && (
106+
{hasOutputErrors && !isScanning && (
177107
<div className="bg-destructive/10 flex items-start gap-3 rounded-lg border border-destructive/20 p-4">
178108
<AlertTriangle className="text-destructive h-5 w-5 flex-shrink-0" />
179109
<div className="flex-1 space-y-1">
180110
<p className="text-destructive text-sm font-medium">Scan completed with errors</p>
181111
<ul className="text-muted-foreground text-xs leading-relaxed">
182112
{outputErrorMessages.slice(0, 5).map((message, index) => (
183-
<li key={`${message}-${index}`}>{message}</li>
113+
<li key={index}>{message}</li>
184114
))}
185115
{outputErrorMessages.length === 0 && (
186116
<li>Encountered an unknown error while processing integration results.</li>
187117
)}
188118
</ul>
189-
{runOutputError.failedIntegrations && runOutputError.failedIntegrations.length > 0 && (
190-
<p className="text-muted-foreground text-xs">
191-
{runOutputError.failedIntegrations.length} integration
192-
{runOutputError.failedIntegrations.length === 1 ? '' : 's'} returned errors.
193-
</p>
194-
)}
195119
</div>
196-
<Button
197-
variant="ghost"
198-
size="sm"
199-
onClick={() => setShowOutputErrorBanner(false)}
200-
className="text-muted-foreground hover:text-foreground h-auto p-1"
201-
>
202-
<X className="h-4 w-4" />
203-
</Button>
204120
</div>
205121
)}
206122

207-
{/* Propagation delay info banner - only when scan succeeds but returns empty output */}
208-
{scanSucceeded && findings.length === 0 && !isScanning && !runOutputError && (
209-
<div className="bg-blue-50 dark:bg-blue-950/20 flex items-center gap-3 rounded-lg border border-blue-200 dark:border-blue-900 p-4">
210-
<Info className="text-blue-600 dark:text-blue-400 h-5 w-5 flex-shrink-0" />
123+
{isFailed && !isScanning && (
124+
<div className="bg-destructive/10 flex items-center gap-3 rounded-lg border border-destructive/20 p-4">
125+
<X className="text-destructive h-5 w-5 flex-shrink-0" />
211126
<div className="flex-1">
212-
<p className="text-blue-900 dark:text-blue-100 text-sm font-medium">
213-
Initial scan complete
214-
</p>
127+
<p className="text-destructive text-sm font-medium">Scan failed</p>
215128
<p className="text-muted-foreground text-xs">
216-
Security findings may take 24-48 hours to appear after enabling cloud security
217-
services. Check back later.
129+
{typeof run?.error === 'object' && run.error && 'message' in run.error
130+
? String(run.error.message)
131+
: 'An error occurred during the scan. Please try again.'}
218132
</p>
219133
</div>
220134
</div>
221135
)}
222136

223-
{showErrorBanner && runHasHardFailure && !isScanning && (
224-
<div className="bg-destructive/10 flex items-center gap-3 rounded-lg border border-destructive/20 p-4">
225-
<X className="text-destructive h-5 w-5 flex-shrink-0" />
137+
{isCompleted && findings.length === 0 && !isScanning && !hasOutputErrors && (
138+
<div className="bg-blue-50 dark:bg-blue-950/20 flex items-center gap-3 rounded-lg border border-blue-200 dark:border-blue-900 p-4">
139+
<Info className="text-blue-600 dark:text-blue-400 h-5 w-5 flex-shrink-0" />
226140
<div className="flex-1">
227-
<p className="text-destructive text-sm font-medium">Scan failed</p>
141+
<p className="text-blue-900 dark:text-blue-100 text-sm font-medium">
142+
Initial scan complete
143+
</p>
228144
<p className="text-muted-foreground text-xs">
229-
{extractCleanErrorMessage(
230-
(typeof run?.error === 'string' && run.error) ||
231-
(run?.error && typeof run.error === 'object' && 'message' in run.error
232-
? String((run.error as { message?: unknown }).message)
233-
: undefined) ||
234-
'An error occurred during the scan. Please try again.',
235-
)}
145+
Security findings may take 24-48 hours to appear after enabling cloud security
146+
services. Check back later.
236147
</p>
237148
</div>
238-
<Button
239-
variant="ghost"
240-
size="sm"
241-
onClick={() => setShowErrorBanner(false)}
242-
className="text-muted-foreground hover:text-foreground h-auto p-1"
243-
>
244-
<X className="h-4 w-4" />
245-
</Button>
246149
</div>
247150
)}
248151

249-
{/* Filters and Run Scan Button */}
250152
<div className="flex items-center justify-between">
251153
{findings.length > 0 ? (
252154
<div className="flex flex-wrap items-center gap-2">
@@ -294,7 +196,6 @@ export function ResultsView({
294196
</Button>
295197
</div>
296198

297-
{/* Results Table */}
298199
{sortedFindings.length > 0 ? (
299200
<FindingsTable findings={sortedFindings} />
300201
) : findings.length > 0 ? (

0 commit comments

Comments
 (0)