Add route and frontend button to run import from prod script
This commit is contained in:
@@ -44,7 +44,8 @@ interface ImportLimits {
|
||||
|
||||
export function DataManagement() {
|
||||
const [isUpdating, setIsUpdating] = useState(false);
|
||||
const [isImporting, setIsImporting] = useState(false);
|
||||
const [isImportingCSV, setIsImportingCSV] = useState(false);
|
||||
const [isImportingProd, setIsImportingProd] = useState(false);
|
||||
const [isResetting, setIsResetting] = useState(false);
|
||||
const [updateProgress, setUpdateProgress] = useState<ImportProgress | null>(null);
|
||||
const [importProgress, setImportProgress] = useState<ImportProgress | null>(null);
|
||||
@@ -76,7 +77,7 @@ export function DataManagement() {
|
||||
|
||||
// Helper to check if any operation is running
|
||||
const isAnyOperationRunning = () => {
|
||||
return isUpdating || isImporting || isResetting || isResettingMetrics || isCalculatingMetrics;
|
||||
return isUpdating || isImportingCSV || isImportingProd || isTestingConnection || isResetting || isCalculatingMetrics;
|
||||
};
|
||||
|
||||
// Helper function to get progress bar color based on status
|
||||
@@ -256,7 +257,7 @@ export function DataManagement() {
|
||||
// Try to reconnect via status check if the operation might still be running
|
||||
if (
|
||||
(type === 'calculate-metrics' && isCalculatingMetrics) ||
|
||||
(type === 'import' && isImporting) ||
|
||||
(type === 'import' && isImportingCSV) ||
|
||||
(type === 'update' && isUpdating) ||
|
||||
(type === 'reset' && isResetting) ||
|
||||
(type === 'reset-metrics' && isResettingMetrics)
|
||||
@@ -341,7 +342,8 @@ export function DataManagement() {
|
||||
if (!otherProgress || otherProgress.status === 'complete' || otherProgress.status === 'error' || otherProgress.status === 'cancelled') {
|
||||
source.close();
|
||||
setEventSource(null);
|
||||
setIsImporting(false);
|
||||
setIsImportingCSV(false);
|
||||
setIsImportingProd(false);
|
||||
|
||||
// Show appropriate toast based on final status
|
||||
if (progressData.status === 'complete') {
|
||||
@@ -433,60 +435,30 @@ export function DataManagement() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleCancel = async (type: 'update' | 'import' | 'reset' | 'reset-metrics' | 'calculate-metrics') => {
|
||||
const handleCancel = async (operation: 'update' | 'import' | 'reset' | 'calculate-metrics') => {
|
||||
try {
|
||||
// Mark this operation as cancelled
|
||||
setCancelledOperations(prev => new Set(prev).add(type));
|
||||
|
||||
// First close any existing event source
|
||||
if (eventSource) {
|
||||
eventSource.close();
|
||||
setEventSource(null);
|
||||
}
|
||||
|
||||
// Send cancel request with the correct endpoint format
|
||||
const response = await fetch(`${config.apiUrl}/csv/cancel?operation=${type}`, {
|
||||
const response = await fetch(`${config.apiUrl}/csv/cancel?operation=${operation}`, {
|
||||
method: 'POST',
|
||||
credentials: 'include'
|
||||
});
|
||||
|
||||
// Set cancelled state immediately
|
||||
switch (type) {
|
||||
case 'import':
|
||||
setLastImportStatus({ ...importProgress, status: 'cancelled' });
|
||||
setImportProgress(null);
|
||||
setIsImporting(false);
|
||||
break;
|
||||
case 'update':
|
||||
setLastUpdateStatus({ ...updateProgress, status: 'cancelled' });
|
||||
setUpdateProgress(null);
|
||||
setIsUpdating(false);
|
||||
break;
|
||||
case 'reset':
|
||||
setLastResetStatus({ ...resetProgress, status: 'cancelled' });
|
||||
setResetProgress(null);
|
||||
setIsResetting(false);
|
||||
break;
|
||||
case 'reset-metrics':
|
||||
setLastResetMetricsStatus({ ...resetMetricsProgress, status: 'cancelled' });
|
||||
setResetMetricsProgress(null);
|
||||
setIsResettingMetrics(false);
|
||||
break;
|
||||
case 'calculate-metrics':
|
||||
setLastMetricsStatus({ ...metricsProgress, status: 'cancelled' });
|
||||
setMetricsProgress(null);
|
||||
setIsCalculatingMetrics(false);
|
||||
break;
|
||||
}
|
||||
|
||||
toast.warning(`${type.charAt(0).toUpperCase() + type.slice(1).replace('-', ' ')} cancelled`);
|
||||
|
||||
|
||||
if (!response.ok) {
|
||||
const data = await response.json().catch(() => ({}));
|
||||
console.error(`Failed to cancel ${type}:`, data.error || 'Unknown error');
|
||||
throw new Error('Failed to cancel operation');
|
||||
}
|
||||
|
||||
// Reset the appropriate state
|
||||
if (operation === 'import') {
|
||||
setIsImportingCSV(false);
|
||||
setIsImportingProd(false);
|
||||
setImportProgress(null);
|
||||
setPurchaseOrdersProgress(null);
|
||||
} else if (operation === 'update') {
|
||||
setIsUpdating(false);
|
||||
setUpdateProgress(null);
|
||||
}
|
||||
// ... other operation states ...
|
||||
} catch (error) {
|
||||
console.error(`Error cancelling ${type}:`, error);
|
||||
toast.error(`Failed to cancel operation: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -539,7 +511,7 @@ export function DataManagement() {
|
||||
|
||||
if (operation.includes('import')) {
|
||||
console.log('Import is running');
|
||||
setIsImporting(true);
|
||||
setIsImportingCSV(true);
|
||||
if (operation.includes('purchase orders')) {
|
||||
setPurchaseOrdersProgress(importData.progress || importData);
|
||||
} else {
|
||||
@@ -628,8 +600,8 @@ export function DataManagement() {
|
||||
};
|
||||
|
||||
const handleImportCSV = async () => {
|
||||
setIsImporting(true);
|
||||
setImportProgress({ status: 'running', operation: 'Starting import process' });
|
||||
setIsImportingCSV(true);
|
||||
setImportProgress({ status: 'running', operation: 'Starting CSV import' });
|
||||
|
||||
try {
|
||||
connectToEventSource('import');
|
||||
@@ -650,40 +622,18 @@ export function DataManagement() {
|
||||
// Start new import
|
||||
const response = await fetch(`${config.apiUrl}/csv/import`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
credentials: 'include',
|
||||
body: JSON.stringify(limits)
|
||||
}).catch(error => {
|
||||
// Ignore network errors as the import might still be running
|
||||
console.log('Import request error (may be timeout):', error);
|
||||
return null;
|
||||
credentials: 'include'
|
||||
});
|
||||
|
||||
// If we got no response but have progress, assume it's still running
|
||||
if (!response && (importProgress?.current || purchaseOrdersProgress?.current)) {
|
||||
console.log('No response but import appears to be running, continuing...');
|
||||
return;
|
||||
}
|
||||
|
||||
// If we got a response, check if it indicates an actual error
|
||||
if (response) {
|
||||
const data = await response.json().catch(() => null);
|
||||
if (!response.ok && data?.error && !data.error.includes('already in progress')) {
|
||||
throw new Error(data.error || 'Failed to start CSV import');
|
||||
}
|
||||
const data = await response.json();
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || 'Failed to start CSV import');
|
||||
}
|
||||
} catch (error) {
|
||||
// Only handle actual errors, not timeouts or connection issues
|
||||
if (error instanceof Error && !error.message.includes('NetworkError') && !error.message.includes('Failed to fetch')) {
|
||||
toast.error(`CSV import failed: ${error.message}`);
|
||||
setIsImporting(false);
|
||||
setImportProgress(null);
|
||||
setPurchaseOrdersProgress(null);
|
||||
} else {
|
||||
console.log('Ignoring network error, import may still be running:', error);
|
||||
}
|
||||
toast.error(`CSV import failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
||||
setIsImportingCSV(false);
|
||||
setImportProgress(null);
|
||||
setPurchaseOrdersProgress(null);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -853,6 +803,61 @@ export function DataManagement() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleImportFromProd = async () => {
|
||||
setIsImportingProd(true);
|
||||
setImportProgress({ status: 'running', operation: 'Starting import from production' });
|
||||
|
||||
try {
|
||||
connectToEventSource('import');
|
||||
|
||||
// First check if import is already running
|
||||
const statusResponse = await fetch(`${config.apiUrl}/csv/status`, {
|
||||
credentials: 'include'
|
||||
}).catch(() => null);
|
||||
|
||||
if (statusResponse) {
|
||||
const statusData = await statusResponse.json().catch(() => null);
|
||||
if (statusData?.active && statusData?.progress) {
|
||||
console.log('Import already running, connecting to existing process');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Start new import
|
||||
const response = await fetch(`${config.apiUrl}/csv/import-from-prod`, {
|
||||
method: 'POST',
|
||||
credentials: 'include'
|
||||
}).catch(error => {
|
||||
console.log('Import request error (may be timeout):', error);
|
||||
return null;
|
||||
});
|
||||
|
||||
// If we got no response but have progress, assume it's still running
|
||||
if (!response && (importProgress?.current || purchaseOrdersProgress?.current)) {
|
||||
console.log('No response but import appears to be running, continuing...');
|
||||
return;
|
||||
}
|
||||
|
||||
// If we got a response, check if it indicates an actual error
|
||||
if (response) {
|
||||
const data = await response.json().catch(() => null);
|
||||
if (!response.ok && data?.error && !data.error.includes('already in progress')) {
|
||||
throw new Error(data.error || 'Failed to start production import');
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// Only handle actual errors, not timeouts or connection issues
|
||||
if (error instanceof Error && !error.message.includes('NetworkError') && !error.message.includes('Failed to fetch')) {
|
||||
toast.error(`Production import failed: ${error.message}`);
|
||||
setIsImportingProd(false);
|
||||
setImportProgress(null);
|
||||
setPurchaseOrdersProgress(null);
|
||||
} else {
|
||||
console.log('Ignoring network error, import may still be running:', error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="max-w-[400px] space-y-4">
|
||||
{/* Test Production Connection Card */}
|
||||
@@ -926,29 +931,47 @@ export function DataManagement() {
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Import Data</CardTitle>
|
||||
<CardDescription>Import current CSV files into database</CardDescription>
|
||||
<CardDescription>Import data from CSV files or production database</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
className="flex-1"
|
||||
className="flex-1 min-w-0"
|
||||
onClick={handleImportCSV}
|
||||
disabled={isAnyOperationRunning()}
|
||||
>
|
||||
{isImporting ? (
|
||||
<>
|
||||
{isImportingCSV ? (
|
||||
<div className="flex items-center justify-center">
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
Importing Data...
|
||||
</>
|
||||
<span className="truncate">Importing CSV...</span>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="flex items-center justify-center">
|
||||
<Upload className="mr-2 h-4 w-4" />
|
||||
Import Data
|
||||
</>
|
||||
<span>Import from CSV</span>
|
||||
</div>
|
||||
)}
|
||||
</Button>
|
||||
|
||||
{isImporting && (
|
||||
<Button
|
||||
className="flex-1 min-w-0"
|
||||
onClick={handleImportFromProd}
|
||||
disabled={isAnyOperationRunning()}
|
||||
>
|
||||
{isImportingProd ? (
|
||||
<div className="flex items-center justify-center">
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
<span className="truncate">Importing Prod...</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center justify-center">
|
||||
<Database className="mr-2 h-4 w-4" />
|
||||
<span>Import from Prod</span>
|
||||
</div>
|
||||
)}
|
||||
</Button>
|
||||
|
||||
{(isImportingCSV || isImportingProd) && (
|
||||
<Button
|
||||
variant="destructive"
|
||||
onClick={() => handleCancel('import')}
|
||||
@@ -958,7 +981,7 @@ export function DataManagement() {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{(isImporting || lastImportStatus) && (
|
||||
{(isImportingCSV || isImportingProd || lastImportStatus) && (
|
||||
<div className="space-y-4">
|
||||
{renderProgress(importProgress || lastImportStatus, 'import')}
|
||||
{renderProgress(purchaseOrdersProgress, 'import')}
|
||||
|
||||
Reference in New Issue
Block a user