mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 13:31:12 -05:00
Fix Retry flashing and dependency validation
This commit is contained in:
@@ -776,6 +776,22 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
|
|||||||
|
|
||||||
const handleRetryFailedItems = async (item: ModerationItem) => {
|
const handleRetryFailedItems = async (item: ModerationItem) => {
|
||||||
setActionLoading(item.id);
|
setActionLoading(item.id);
|
||||||
|
|
||||||
|
// Optimistic UI update - remove from queue immediately
|
||||||
|
const shouldRemove = (
|
||||||
|
activeStatusFilter === 'pending' ||
|
||||||
|
activeStatusFilter === 'flagged' ||
|
||||||
|
activeStatusFilter === 'partially_approved'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (shouldRemove) {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
setItems(prev => prev.filter(i => i.id !== item.id));
|
||||||
|
recentlyRemovedRef.current.add(item.id);
|
||||||
|
setTimeout(() => recentlyRemovedRef.current.delete(item.id), 3000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Fetch failed/rejected submission items
|
// Fetch failed/rejected submission items
|
||||||
const { data: failedItems, error: fetchError } = await supabase
|
const { data: failedItems, error: fetchError } = await supabase
|
||||||
@@ -812,7 +828,6 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
|
|||||||
description: `Processed ${failedItems.length} failed item(s)`,
|
description: `Processed ${failedItems.length} failed item(s)`,
|
||||||
});
|
});
|
||||||
|
|
||||||
// No refresh needed - item already updated optimistically
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('Error retrying failed items:', error);
|
console.error('Error retrying failed items:', error);
|
||||||
toast({
|
toast({
|
||||||
|
|||||||
@@ -197,6 +197,7 @@ serve(async (req) => {
|
|||||||
itemType: string;
|
itemType: string;
|
||||||
success: boolean;
|
success: boolean;
|
||||||
error?: string;
|
error?: string;
|
||||||
|
isDependencyFailure?: boolean;
|
||||||
}> = [];
|
}> = [];
|
||||||
|
|
||||||
// Process items in order
|
// Process items in order
|
||||||
@@ -285,23 +286,71 @@ serve(async (req) => {
|
|||||||
console.log(`Successfully approved item ${item.id} -> entity ${entityId}`);
|
console.log(`Successfully approved item ${item.id} -> entity ${entityId}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error processing item ${item.id}:`, error);
|
console.error(`Error processing item ${item.id}:`, error);
|
||||||
|
|
||||||
|
const isDependencyError = error instanceof Error && (
|
||||||
|
error.message.includes('Missing dependency') ||
|
||||||
|
error.message.includes('depends on') ||
|
||||||
|
error.message.includes('Circular dependency')
|
||||||
|
);
|
||||||
|
|
||||||
approvalResults.push({
|
approvalResults.push({
|
||||||
itemId: item.id,
|
itemId: item.id,
|
||||||
itemType: item.item_type,
|
itemType: item.item_type,
|
||||||
success: false,
|
success: false,
|
||||||
error: error instanceof Error ? error.message : 'Unknown error'
|
error: error instanceof Error ? error.message : 'Unknown error',
|
||||||
|
isDependencyFailure: isDependencyError
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Mark item as rejected in submission_items
|
||||||
|
const { error: markRejectedError } = await supabase
|
||||||
|
.from('submission_items')
|
||||||
|
.update({
|
||||||
|
status: 'rejected',
|
||||||
|
rejection_reason: error instanceof Error ? error.message : 'Unknown error',
|
||||||
|
updated_at: new Date().toISOString()
|
||||||
|
})
|
||||||
|
.eq('id', item.id);
|
||||||
|
|
||||||
|
if (markRejectedError) {
|
||||||
|
console.error(`Failed to mark item ${item.id} as rejected:`, markRejectedError);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update submission status
|
// Check if any failures were dependency-related
|
||||||
|
const hasDependencyFailure = approvalResults.some(r =>
|
||||||
|
!r.success && r.isDependencyFailure
|
||||||
|
);
|
||||||
|
|
||||||
const allApproved = approvalResults.every(r => r.success);
|
const allApproved = approvalResults.every(r => r.success);
|
||||||
|
const someApproved = approvalResults.some(r => r.success);
|
||||||
|
const allFailed = approvalResults.every(r => !r.success);
|
||||||
|
|
||||||
|
// Determine final status:
|
||||||
|
// - If dependency validation failed: keep pending for escalation
|
||||||
|
// - If all approved: approved
|
||||||
|
// - If some approved: partially_approved
|
||||||
|
// - If all failed but no dependency issues: rejected (can retry)
|
||||||
|
const finalStatus = hasDependencyFailure && !someApproved
|
||||||
|
? 'pending' // Keep pending for escalation only
|
||||||
|
: allApproved
|
||||||
|
? 'approved'
|
||||||
|
: allFailed
|
||||||
|
? 'rejected' // Total failure, allow retry
|
||||||
|
: 'partially_approved'; // Mixed results
|
||||||
|
|
||||||
|
const reviewerNotes = hasDependencyFailure && !someApproved
|
||||||
|
? 'Submission has unresolved dependencies. Escalation required.'
|
||||||
|
: undefined;
|
||||||
|
|
||||||
const { error: updateError } = await supabase
|
const { error: updateError } = await supabase
|
||||||
.from('content_submissions')
|
.from('content_submissions')
|
||||||
.update({
|
.update({
|
||||||
status: allApproved ? 'approved' : 'partially_approved',
|
status: finalStatus,
|
||||||
reviewer_id: authenticatedUserId,
|
reviewer_id: authenticatedUserId,
|
||||||
reviewed_at: new Date().toISOString()
|
reviewed_at: new Date().toISOString(),
|
||||||
|
reviewer_notes: reviewerNotes,
|
||||||
|
escalated: hasDependencyFailure && !someApproved ? true : undefined
|
||||||
})
|
})
|
||||||
.eq('id', submissionId);
|
.eq('id', submissionId);
|
||||||
|
|
||||||
@@ -313,7 +362,7 @@ serve(async (req) => {
|
|||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
success: true,
|
success: true,
|
||||||
results: approvalResults,
|
results: approvalResults,
|
||||||
submissionStatus: allApproved ? 'approved' : 'partially_approved'
|
submissionStatus: finalStatus
|
||||||
}),
|
}),
|
||||||
{ headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
{ headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
||||||
);
|
);
|
||||||
@@ -335,7 +384,10 @@ function topologicalSort(items: any[]): any[] {
|
|||||||
const visit = (item: any) => {
|
const visit = (item: any) => {
|
||||||
if (visited.has(item.id)) return;
|
if (visited.has(item.id)) return;
|
||||||
if (visiting.has(item.id)) {
|
if (visiting.has(item.id)) {
|
||||||
throw new Error(`Circular dependency detected for item ${item.id}`);
|
throw new Error(
|
||||||
|
`Circular dependency detected: item ${item.id} (${item.item_type}) ` +
|
||||||
|
`creates a dependency loop. This submission requires escalation.`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
visiting.add(item.id);
|
visiting.add(item.id);
|
||||||
@@ -343,7 +395,11 @@ function topologicalSort(items: any[]): any[] {
|
|||||||
if (item.depends_on) {
|
if (item.depends_on) {
|
||||||
const parent = items.find(i => i.id === item.depends_on);
|
const parent = items.find(i => i.id === item.depends_on);
|
||||||
if (!parent) {
|
if (!parent) {
|
||||||
throw new Error(`Missing dependency: item ${item.id} depends on ${item.depends_on} which is not in the submission`);
|
throw new Error(
|
||||||
|
`Missing dependency: item ${item.id} (${item.item_type}) ` +
|
||||||
|
`depends on ${item.depends_on} which is not in this submission or has not been approved. ` +
|
||||||
|
`This submission requires escalation.`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
visit(parent);
|
visit(parent);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user