Fix composite submission error logging

This commit is contained in:
gpt-engineer-app[bot]
2025-11-05 14:20:56 +00:00
parent e773ca58d1
commit 116eaa2635
2 changed files with 164 additions and 68 deletions

View File

@@ -8,6 +8,7 @@ import type { CompanyDatabaseRecord, TimelineEventDatabaseRecord } from '@/types
import { logger } from './logger'; import { logger } from './logger';
import { handleError } from './errorHandler'; import { handleError } from './errorHandler';
import type { TimelineEventFormData, EntityType } from '@/types/timeline'; import type { TimelineEventFormData, EntityType } from '@/types/timeline';
import { breadcrumb } from './errorBreadcrumbs';
import { import {
validateParkCreateFields, validateParkCreateFields,
validateRideCreateFields, validateRideCreateFields,
@@ -202,20 +203,42 @@ async function submitCompositeCreation(
dependencies: CompositeSubmissionDependency[], dependencies: CompositeSubmissionDependency[],
userId: string userId: string
): Promise<{ submitted: boolean; submissionId: string }> { ): Promise<{ submitted: boolean; submissionId: string }> {
try {
breadcrumb.userAction('Start composite submission', 'submitCompositeCreation', {
primaryType: primaryEntity.type,
dependencyCount: dependencies.length,
userId
});
// Check if user is banned // Check if user is banned
const { data: profile } = await supabase breadcrumb.apiCall('profiles', 'SELECT');
try {
const { data: profile, error } = await supabase
.from('profiles') .from('profiles')
.select('banned') .select('banned')
.eq('user_id', userId) .eq('user_id', userId)
.single(); .single();
if (error) {
throw new Error(`Failed to check user status: ${error.message}`);
}
if (profile?.banned) { if (profile?.banned) {
throw new Error('Account suspended. Contact support for assistance.'); throw new Error('Account suspended. Contact support for assistance.');
} }
} catch (error) {
throw error instanceof Error ? error : new Error(`User check failed: ${String(error)}`);
}
// Upload all pending images for all entities // Upload all pending images for all entities
breadcrumb.userAction('Upload images', 'submitCompositeCreation', {
totalImages: dependencies.reduce((sum, dep) => sum + (dep.data.images?.uploaded?.length || 0), 0) +
(primaryEntity.data.images?.uploaded?.length || 0)
});
const uploadedEntities = await Promise.all([ const uploadedEntities = await Promise.all([
...dependencies.map(async (dep) => { ...dependencies.map(async (dep, index) => {
try {
if (dep.data.images?.uploaded && dep.data.images.uploaded.length > 0) { if (dep.data.images?.uploaded && dep.data.images.uploaded.length > 0) {
const uploadedImages = await uploadPendingImages(dep.data.images.uploaded); const uploadedImages = await uploadPendingImages(dep.data.images.uploaded);
return { return {
@@ -227,8 +250,15 @@ async function submitCompositeCreation(
}; };
} }
return dep; return dep;
} catch (error) {
const errorMsg = error instanceof Error ? error.message : String(error);
throw new Error(
`Failed to upload images for ${dep.type} "${dep.data.name || 'unnamed'}": ${errorMsg}`
);
}
}), }),
(async () => { (async () => {
try {
if (primaryEntity.data.images?.uploaded && primaryEntity.data.images.uploaded.length > 0) { if (primaryEntity.data.images?.uploaded && primaryEntity.data.images.uploaded.length > 0) {
const uploadedImages = await uploadPendingImages(primaryEntity.data.images.uploaded); const uploadedImages = await uploadPendingImages(primaryEntity.data.images.uploaded);
return { return {
@@ -240,12 +270,32 @@ async function submitCompositeCreation(
}; };
} }
return primaryEntity; return primaryEntity;
} catch (error) {
const errorMsg = error instanceof Error ? error.message : String(error);
throw new Error(
`Failed to upload images for ${primaryEntity.type} "${primaryEntity.data.name || 'unnamed'}": ${errorMsg}`
);
}
})() })()
]); ]);
const uploadedDependencies = uploadedEntities.slice(0, -1) as CompositeSubmissionDependency[]; const uploadedDependencies = uploadedEntities.slice(0, -1) as CompositeSubmissionDependency[];
const uploadedPrimary = uploadedEntities[uploadedEntities.length - 1] as typeof primaryEntity; const uploadedPrimary = uploadedEntities[uploadedEntities.length - 1] as typeof primaryEntity;
// Validate dependencies structure
breadcrumb.stateChange('Validating dependencies', {
dependencyCount: uploadedDependencies.length
});
for (const dep of uploadedDependencies) {
if (!dep.type) throw new Error('Dependency missing type');
if (!dep.tempId) throw new Error('Dependency missing tempId');
if (!dep.data) throw new Error('Dependency missing data');
if (dep.type === 'company' && !dep.companyType) {
throw new Error(`Company dependency "${dep.data.name || 'unnamed'}" missing companyType`);
}
}
// Build submission items array with dependencies first // Build submission items array with dependencies first
const submissionItems: any[] = []; const submissionItems: any[] = [];
const tempIdMap = new Map<string, number>(); // Maps tempId to order_index const tempIdMap = new Map<string, number>(); // Maps tempId to order_index
@@ -371,6 +421,7 @@ async function submitCompositeCreation(
} }
// Use RPC to create submission with items atomically with retry logic // Use RPC to create submission with items atomically with retry logic
breadcrumb.apiCall('create_submission_with_items', 'RPC');
const { withRetry } = await import('./retryHelpers'); const { withRetry } = await import('./retryHelpers');
const { toast } = await import('@/hooks/use-toast'); const { toast } = await import('@/hooks/use-toast');
@@ -463,7 +514,24 @@ async function submitCompositeCreation(
throw error; throw error;
}); });
breadcrumb.stateChange('Composite submission successful', {
submissionId: result
});
return { submitted: true, submissionId: result }; return { submitted: true, submissionId: result };
} catch (error) {
// Ensure error is always an Error instance with context
const enrichedError = error instanceof Error
? error
: new Error(`Composite submission failed: ${String(error)}`);
// Attach metadata for better debugging
(enrichedError as any).originalError = error;
(enrichedError as any).primaryType = primaryEntity?.type;
(enrichedError as any).dependencyCount = dependencies?.length;
throw enrichedError;
}
} }
/** /**

View File

@@ -34,6 +34,7 @@ export const handleError = (
let errorMessage: string; let errorMessage: string;
let stack: string | undefined; let stack: string | undefined;
let errorName = 'UnknownError'; let errorName = 'UnknownError';
let supabaseErrorDetails: Record<string, any> | undefined;
if (error instanceof Error) { if (error instanceof Error) {
errorMessage = error instanceof AppError errorMessage = error instanceof AppError
@@ -41,6 +42,15 @@ export const handleError = (
: error.message; : error.message;
stack = error.stack; stack = error.stack;
errorName = error.name; errorName = error.name;
// Check if Error instance has attached Supabase metadata
if ((error as any).supabaseCode) {
supabaseErrorDetails = {
code: (error as any).supabaseCode,
details: (error as any).supabaseDetails,
hint: (error as any).supabaseHint
};
}
} else if (error && typeof error === 'object') { } else if (error && typeof error === 'object') {
// Handle Supabase errors (plain objects with message/code/details) // Handle Supabase errors (plain objects with message/code/details)
const supabaseError = error as { const supabaseError = error as {
@@ -48,13 +58,24 @@ export const handleError = (
code?: string; code?: string;
details?: string; details?: string;
hint?: string; hint?: string;
stack?: string;
}; };
errorMessage = supabaseError.message || 'An unexpected error occurred'; errorMessage = supabaseError.message || 'An unexpected error occurred';
errorName = 'SupabaseError'; errorName = 'SupabaseError';
// Capture Supabase error details for metadata
supabaseErrorDetails = {
code: supabaseError.code,
details: supabaseError.details,
hint: supabaseError.hint
};
// Try to extract stack from object
if (supabaseError.stack && typeof supabaseError.stack === 'string') {
stack = supabaseError.stack;
} else if (supabaseError.code || supabaseError.details || supabaseError.hint) {
// Create synthetic stack trace for Supabase errors to aid debugging // Create synthetic stack trace for Supabase errors to aid debugging
if (supabaseError.code || supabaseError.details || supabaseError.hint) {
const stackParts = [ const stackParts = [
`SupabaseError: ${errorMessage}`, `SupabaseError: ${errorMessage}`,
supabaseError.code ? ` Code: ${supabaseError.code}` : null, supabaseError.code ? ` Code: ${supabaseError.code}` : null,
@@ -68,8 +89,12 @@ export const handleError = (
} }
} else if (typeof error === 'string') { } else if (typeof error === 'string') {
errorMessage = error; errorMessage = error;
// Generate synthetic stack trace for string errors
stack = new Error().stack?.replace(/^Error\n/, `StringError: ${error}\n`);
} else { } else {
errorMessage = 'An unexpected error occurred'; errorMessage = 'An unexpected error occurred';
// Generate synthetic stack trace for unknown error types
stack = new Error().stack?.replace(/^Error\n/, `UnknownError: ${String(error)}\n`);
} }
// Log to console/monitoring with enhanced debugging // Log to console/monitoring with enhanced debugging
@@ -84,6 +109,7 @@ export const handleError = (
errorConstructor: error?.constructor?.name, errorConstructor: error?.constructor?.name,
hasStack: !!stack, hasStack: !!stack,
isSyntheticStack: !!(error && typeof error === 'object' && !(error instanceof Error) && stack), isSyntheticStack: !!(error && typeof error === 'object' && !(error instanceof Error) && stack),
supabaseError: supabaseErrorDetails,
}); });
// Additional debug logging when stack is missing // Additional debug logging when stack is missing
@@ -114,11 +140,13 @@ export const handleError = (
p_error_stack: stack, p_error_stack: stack,
p_user_agent: navigator.userAgent, p_user_agent: navigator.userAgent,
p_breadcrumbs: JSON.stringify({ p_breadcrumbs: JSON.stringify({
...breadcrumbs, breadcrumbs,
isRetry: context.metadata?.isRetry || false, isRetry: context.metadata?.isRetry || false,
attempt: context.metadata?.attempt, attempt: context.metadata?.attempt,
retriesExhausted: context.metadata?.retriesExhausted || false, retriesExhausted: context.metadata?.retriesExhausted || false,
circuitBreakerState: context.metadata?.circuitState, circuitBreakerState: context.metadata?.circuitState,
supabaseError: supabaseErrorDetails,
metadata: context.metadata
}), }),
p_timezone: envContext.timezone, p_timezone: envContext.timezone,
p_referrer: document.referrer || undefined, p_referrer: document.referrer || undefined,