Integrate transaction resilience hook

Integrate the `useTransactionResilience` hook into `SubmissionReviewManager.tsx` to add timeout detection, auto-release functionality, and idempotency key management to moderation actions. The `handleApprove` and `handleReject` functions have been updated to use the `executeTransaction` wrapper for these operations.
This commit is contained in:
gpt-engineer-app[bot]
2025-11-07 15:36:53 +00:00
parent 34dbe2e262
commit fc8631ff0b

View File

@@ -6,6 +6,7 @@ import { handleError, getErrorMessage } from '@/lib/errorHandler';
import { invokeWithTracking } from '@/lib/edgeFunctionTracking'; import { invokeWithTracking } from '@/lib/edgeFunctionTracking';
import { moderationReducer, canApprove, canReject, hasActiveLock } from '@/lib/moderationStateMachine'; import { moderationReducer, canApprove, canReject, hasActiveLock } from '@/lib/moderationStateMachine';
import { useLockMonitor } from '@/lib/moderation/lockMonitor'; import { useLockMonitor } from '@/lib/moderation/lockMonitor';
import { useTransactionResilience } from '@/hooks/useTransactionResilience';
import { import {
fetchSubmissionItems, fetchSubmissionItems,
buildDependencyTree, buildDependencyTree,
@@ -92,6 +93,15 @@ export function SubmissionReviewManager({
// Lock monitoring integration // Lock monitoring integration
const { extendLock } = useLockMonitor(state, dispatch, submissionId); const { extendLock } = useLockMonitor(state, dispatch, submissionId);
// Transaction resilience (timeout detection & auto-release)
const { executeTransaction } = useTransactionResilience({
submissionId,
timeoutMs: 30000, // 30s timeout
autoReleaseOnUnload: true,
autoReleaseOnInactivity: true,
inactivityMinutes: 10,
});
// Moderation actions // Moderation actions
const { escalateSubmission } = useModerationActions({ const { escalateSubmission } = useModerationActions({
user, user,
@@ -230,6 +240,7 @@ export function SubmissionReviewManager({
} }
const selectedItems = items.filter(item => selectedItemIds.has(item.id)); const selectedItems = items.filter(item => selectedItemIds.has(item.id));
const selectedIds = Array.from(selectedItemIds);
// Transition: reviewing → approving // Transition: reviewing → approving
dispatch({ type: 'START_APPROVAL' }); dispatch({ type: 'START_APPROVAL' });
@@ -259,6 +270,7 @@ export function SubmissionReviewManager({
})) }))
); );
setValidationResults(validationResultsMap); setValidationResults(validationResultsMap);
// Check for blocking errors // Check for blocking errors
@@ -324,15 +336,20 @@ export function SubmissionReviewManager({
return; // Ask for confirmation return; // Ask for confirmation
} }
// Proceed with approval // Proceed with approval - wrapped with transaction resilience
await executeTransaction(
'approval',
selectedIds,
async (idempotencyKey) => {
const { supabase } = await import('@/integrations/supabase/client'); const { supabase } = await import('@/integrations/supabase/client');
// Call the edge function for backend processing // Call the edge function for backend processing
const { data, error, requestId } = await invokeWithTracking( const { data, error, requestId } = await invokeWithTracking(
'process-selective-approval', 'process-selective-approval',
{ {
itemIds: Array.from(selectedItemIds), itemIds: selectedIds,
submissionId submissionId,
idempotencyKey, // Pass idempotency key to edge function
}, },
user?.id user?.id
); );
@@ -350,7 +367,7 @@ export function SubmissionReviewManager({
toast({ toast({
title: 'Items Approved', title: 'Items Approved',
description: `Successfully approved ${selectedItemIds.size} item(s)${requestId ? ` (Request: ${requestId.substring(0, 8)})` : ''}`, description: `Successfully approved ${selectedIds.length} item(s)${requestId ? ` (Request: ${requestId.substring(0, 8)})` : ''}`,
}); });
interface ApprovalResult { success: boolean; item_id: string; error?: string } interface ApprovalResult { success: boolean; item_id: string; error?: string }
@@ -374,7 +391,7 @@ export function SubmissionReviewManager({
// If ALL items failed, don't close dialog - show errors // If ALL items failed, don't close dialog - show errors
if (allFailed) { if (allFailed) {
dispatch({ type: 'ERROR', payload: { error: 'All items failed' } }); dispatch({ type: 'ERROR', payload: { error: 'All items failed' } });
return; return data;
} }
// Reset warning confirmation state after approval // Reset warning confirmation state after approval
@@ -382,6 +399,10 @@ export function SubmissionReviewManager({
onComplete(); onComplete();
onOpenChange(false); onOpenChange(false);
return data;
}
);
} catch (error: unknown) { } catch (error: unknown) {
dispatch({ type: 'ERROR', payload: { error: getErrorMessage(error) } }); dispatch({ type: 'ERROR', payload: { error: getErrorMessage(error) } });
handleError(error, { handleError(error, {
@@ -438,11 +459,18 @@ export function SubmissionReviewManager({
if (!user?.id) return; if (!user?.id) return;
const selectedItems = items.filter(item => selectedItemIds.has(item.id));
const selectedIds = selectedItems.map(item => item.id);
// Transition: reviewing → rejecting // Transition: reviewing → rejecting
dispatch({ type: 'START_REJECTION' }); dispatch({ type: 'START_REJECTION' });
try { try {
const selectedItems = items.filter(item => selectedItemIds.has(item.id)); // Wrap rejection with transaction resilience
await executeTransaction(
'rejection',
selectedIds,
async (idempotencyKey) => {
await rejectSubmissionItems(selectedItems, reason, user.id, cascade); await rejectSubmissionItems(selectedItems, reason, user.id, cascade);
// Transition: rejecting → complete // Transition: rejecting → complete
@@ -455,6 +483,10 @@ export function SubmissionReviewManager({
onComplete(); onComplete();
onOpenChange(false); onOpenChange(false);
return { success: true };
}
);
} catch (error: unknown) { } catch (error: unknown) {
dispatch({ type: 'ERROR', payload: { error: getErrorMessage(error) } }); dispatch({ type: 'ERROR', payload: { error: getErrorMessage(error) } });
handleError(error, { handleError(error, {