Optimize metrics import and split off metrics import functions (untested)
This commit is contained in:
@@ -58,9 +58,10 @@ export function Settings() {
|
||||
orders: 0,
|
||||
purchaseOrders: 0
|
||||
});
|
||||
const [isCreatingSnapshot, setIsCreatingSnapshot] = useState(false);
|
||||
const [isRestoringSnapshot, setIsRestoringSnapshot] = useState(false);
|
||||
const [snapshotProgress, setSnapshotProgress] = useState<ImportProgress | null>(null);
|
||||
const [isResettingMetrics, setIsResettingMetrics] = useState(false);
|
||||
const [resetMetricsProgress, setResetMetricsProgress] = useState<ImportProgress | null>(null);
|
||||
const [isCalculatingMetrics, setIsCalculatingMetrics] = useState(false);
|
||||
const [metricsProgress, setMetricsProgress] = useState<ImportProgress | null>(null);
|
||||
|
||||
// Helper function to update progress state
|
||||
const updateProgressState = (progressData: any) => {
|
||||
@@ -92,13 +93,11 @@ export function Settings() {
|
||||
setPurchaseOrdersProgress(prev => ({ ...prev, ...progressUpdate }));
|
||||
} else if (operation.includes('metrics') || operation.includes('vendor metrics')) {
|
||||
setImportProgress(prev => ({ ...prev, ...progressUpdate }));
|
||||
} else if (operation.includes('snapshot')) {
|
||||
setSnapshotProgress(prev => ({ ...prev, ...progressUpdate }));
|
||||
}
|
||||
};
|
||||
|
||||
// Helper to connect to event source
|
||||
const connectToEventSource = useCallback((type: 'update' | 'import' | 'reset' | 'snapshot') => {
|
||||
const connectToEventSource = useCallback((type: 'update' | 'import' | 'reset' | 'reset-metrics' | 'calculate-metrics') => {
|
||||
if (eventSource) {
|
||||
eventSource.close();
|
||||
}
|
||||
@@ -122,6 +121,8 @@ export function Settings() {
|
||||
// For non-import operations, use the existing logic
|
||||
const setProgress = type === 'update' ? setUpdateProgress :
|
||||
type === 'reset' ? setResetProgress :
|
||||
type === 'reset-metrics' ? setResetMetricsProgress :
|
||||
type === 'calculate-metrics' ? setMetricsProgress :
|
||||
setImportProgress;
|
||||
setProgress(prev => ({
|
||||
...prev,
|
||||
@@ -147,6 +148,8 @@ export function Settings() {
|
||||
if (type === 'update') setIsUpdating(true);
|
||||
else if (type === 'import') setIsImporting(true);
|
||||
else if (type === 'reset') setIsResetting(true);
|
||||
else if (type === 'reset-metrics') setIsResettingMetrics(true);
|
||||
else if (type === 'calculate-metrics') setIsCalculatingMetrics(true);
|
||||
}
|
||||
|
||||
if (progressData.status === 'complete') {
|
||||
@@ -164,6 +167,12 @@ export function Settings() {
|
||||
} else if (type === 'reset') {
|
||||
setIsResetting(false);
|
||||
setResetProgress(null);
|
||||
} else if (type === 'reset-metrics') {
|
||||
setIsResettingMetrics(false);
|
||||
setResetMetricsProgress(null);
|
||||
} else if (type === 'calculate-metrics') {
|
||||
setIsCalculatingMetrics(false);
|
||||
setMetricsProgress(null);
|
||||
}
|
||||
|
||||
if (!progressData.operation?.includes('cancelled')) {
|
||||
@@ -180,6 +189,10 @@ export function Settings() {
|
||||
setIsImporting(false);
|
||||
} else if (type === 'reset') {
|
||||
setIsResetting(false);
|
||||
} else if (type === 'reset-metrics') {
|
||||
setIsResettingMetrics(false);
|
||||
} else if (type === 'calculate-metrics') {
|
||||
setIsCalculatingMetrics(false);
|
||||
}
|
||||
|
||||
handleError(`${type.charAt(0).toUpperCase() + type.slice(1)}`, progressData.error || 'Unknown error');
|
||||
@@ -188,7 +201,7 @@ export function Settings() {
|
||||
console.error('Error parsing event data:', error);
|
||||
}
|
||||
};
|
||||
}, []); // Remove dependencies that might prevent initial connection
|
||||
}, []);
|
||||
|
||||
// Check for active operations on mount
|
||||
useEffect(() => {
|
||||
@@ -203,13 +216,15 @@ export function Settings() {
|
||||
|
||||
if (data.active) {
|
||||
// Try to determine the operation type from progress if available
|
||||
let operationType: 'update' | 'import' | 'reset' | null = null;
|
||||
let operationType: 'update' | 'import' | 'reset' | 'reset-metrics' | 'calculate-metrics' | null = null;
|
||||
|
||||
if (data.progress?.operation) {
|
||||
const operation = data.progress.operation.toLowerCase();
|
||||
if (operation.includes('update')) operationType = 'update';
|
||||
else if (operation.includes('import')) operationType = 'import';
|
||||
else if (operation.includes('reset')) operationType = 'reset';
|
||||
else if (operation.includes('reset-metrics')) operationType = 'reset-metrics';
|
||||
else if (operation.includes('calculate-metrics')) operationType = 'calculate-metrics';
|
||||
} else {
|
||||
// If no progress data, try to connect to import stream by default
|
||||
// since that's the most common long-running operation
|
||||
@@ -242,6 +257,22 @@ export function Settings() {
|
||||
status: data.progress.status || 'running'
|
||||
});
|
||||
}
|
||||
} else if (operationType === 'reset-metrics') {
|
||||
setIsResettingMetrics(true);
|
||||
if (data.progress) {
|
||||
setResetMetricsProgress({
|
||||
...data.progress,
|
||||
status: data.progress.status || 'running'
|
||||
});
|
||||
}
|
||||
} else if (operationType === 'calculate-metrics') {
|
||||
setIsCalculatingMetrics(true);
|
||||
if (data.progress) {
|
||||
setMetricsProgress({
|
||||
...data.progress,
|
||||
status: data.progress.status || 'running'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Connect to the appropriate event source
|
||||
@@ -258,6 +289,14 @@ export function Settings() {
|
||||
}, []); // Remove connectToEventSource dependency to ensure it runs on mount
|
||||
|
||||
// Clean up function to reset state
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (eventSource) {
|
||||
console.log('Cleaning up event source'); // Debug log
|
||||
eventSource.close();
|
||||
}
|
||||
};
|
||||
}, [eventSource]);
|
||||
|
||||
const handleCancel = async () => {
|
||||
// Determine which operation is running first
|
||||
@@ -396,72 +435,83 @@ export function Settings() {
|
||||
}
|
||||
};
|
||||
|
||||
// Add handlers for snapshot operations
|
||||
const handleCreateSnapshot = async () => {
|
||||
const handleResetMetrics = async () => {
|
||||
setIsResettingMetrics(true);
|
||||
setResetMetricsProgress({ status: 'running', operation: 'Starting metrics reset' });
|
||||
|
||||
try {
|
||||
setIsCreatingSnapshot(true);
|
||||
setSnapshotProgress({ status: 'running', operation: 'Creating test data snapshot' });
|
||||
|
||||
// Connect to SSE for progress updates
|
||||
connectToEventSource('snapshot');
|
||||
connectToEventSource('reset-metrics');
|
||||
|
||||
const response = await fetch(`${config.apiUrl}/snapshot/create`, {
|
||||
// Make the reset request
|
||||
const response = await fetch(`${config.apiUrl}/csv/reset-metrics`, {
|
||||
method: 'POST',
|
||||
credentials: 'include'
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to create snapshot');
|
||||
const data = await response.json().catch(() => ({}));
|
||||
throw new Error(data.error || 'Failed to reset metrics');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error creating snapshot:', error);
|
||||
if (eventSource) {
|
||||
eventSource.close();
|
||||
setEventSource(null);
|
||||
}
|
||||
setIsCreatingSnapshot(false);
|
||||
setSnapshotProgress(null);
|
||||
toast.error('Failed to create snapshot');
|
||||
setIsResettingMetrics(false);
|
||||
setResetMetricsProgress(null);
|
||||
handleError('Metrics reset', error instanceof Error ? error.message : 'Unknown error');
|
||||
}
|
||||
};
|
||||
|
||||
const handleRestoreSnapshot = async () => {
|
||||
const handleCalculateMetrics = async () => {
|
||||
setIsCalculatingMetrics(true);
|
||||
setMetricsProgress({ status: 'running', operation: 'Starting metrics calculation' });
|
||||
|
||||
try {
|
||||
setIsRestoringSnapshot(true);
|
||||
setSnapshotProgress({ status: 'running', operation: 'Restoring test data snapshot' });
|
||||
|
||||
// Connect to SSE for progress updates
|
||||
connectToEventSource('snapshot');
|
||||
// Connect to SSE for progress updates
|
||||
connectToEventSource('calculate-metrics');
|
||||
|
||||
const response = await fetch(`${config.apiUrl}/snapshot/restore`, {
|
||||
method: 'POST',
|
||||
credentials: 'include'
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to restore snapshot');
|
||||
}
|
||||
// Make the calculation request
|
||||
const response = await fetch(`${config.apiUrl}/csv/calculate-metrics`, {
|
||||
method: 'POST',
|
||||
credentials: 'include'
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const data = await response.json().catch(() => ({}));
|
||||
throw new Error(data.error || 'Failed to calculate metrics');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error restoring snapshot:', error);
|
||||
if (eventSource) {
|
||||
eventSource.close();
|
||||
setEventSource(null);
|
||||
}
|
||||
setIsRestoringSnapshot(false);
|
||||
setSnapshotProgress(null);
|
||||
toast.error('Failed to restore snapshot');
|
||||
if (eventSource) {
|
||||
eventSource.close();
|
||||
setEventSource(null);
|
||||
}
|
||||
setIsCalculatingMetrics(false);
|
||||
setMetricsProgress(null);
|
||||
handleError('Metrics calculation', error instanceof Error ? error.message : 'Unknown error');
|
||||
}
|
||||
};
|
||||
|
||||
// Cleanup on unmount
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (eventSource) {
|
||||
console.log('Cleaning up event source'); // Debug log
|
||||
eventSource.close();
|
||||
}
|
||||
};
|
||||
}, [eventSource]);
|
||||
// Update the message handlers to use toast
|
||||
const handleComplete = (operation: string) => {
|
||||
toast.success(`${operation} completed successfully`);
|
||||
};
|
||||
|
||||
const handleError = (operation: string, error: string) => {
|
||||
// Skip error toast if we're cancelling or if it's a cancellation error
|
||||
if (error.includes('cancelled') ||
|
||||
error.includes('Process exited with code 143') ||
|
||||
error.includes('Operation cancelled') ||
|
||||
error.includes('500 Internal Server Error') ||
|
||||
// Skip "Failed to start" errors if we have active progress
|
||||
(error.includes('Failed to start CSV import') && (importProgress || purchaseOrdersProgress)) ||
|
||||
// Skip connection errors if we have active progress
|
||||
(error.includes('Failed to fetch') && (importProgress || purchaseOrdersProgress))) {
|
||||
return;
|
||||
}
|
||||
toast.error(`${operation} failed: ${error}`);
|
||||
};
|
||||
|
||||
const renderProgress = (progress: ImportProgress | null) => {
|
||||
if (!progress) return null;
|
||||
@@ -523,26 +573,6 @@ export function Settings() {
|
||||
);
|
||||
};
|
||||
|
||||
const handleError = (operation: string, error: string) => {
|
||||
// Skip error toast if we're cancelling or if it's a cancellation error
|
||||
if (error.includes('cancelled') ||
|
||||
error.includes('Process exited with code 143') ||
|
||||
error.includes('Operation cancelled') ||
|
||||
error.includes('500 Internal Server Error') ||
|
||||
// Skip "Failed to start" errors if we have active progress
|
||||
(error.includes('Failed to start CSV import') && (importProgress || purchaseOrdersProgress)) ||
|
||||
// Skip connection errors if we have active progress
|
||||
(error.includes('Failed to fetch') && (importProgress || purchaseOrdersProgress))) {
|
||||
return;
|
||||
}
|
||||
toast.error(`${operation} failed: ${error}`);
|
||||
};
|
||||
|
||||
// Update the message handlers to use toast
|
||||
const handleComplete = (operation: string) => {
|
||||
toast.success(`${operation} completed successfully`);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-8 space-y-8">
|
||||
<div className="flex items-center justify-between">
|
||||
@@ -718,32 +748,62 @@ export function Settings() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Reset Database Card */}
|
||||
{/* Database Management Card */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Reset Database</CardTitle>
|
||||
<CardDescription>Drop all tables and recreate the database schema. This will delete ALL data.</CardDescription>
|
||||
<CardTitle>Database Management</CardTitle>
|
||||
<CardDescription>Reset database or metrics tables</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button variant="destructive" disabled={isResetting || isImporting || isUpdating}>
|
||||
Reset Database
|
||||
</Button>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
This action cannot be undone. This will permanently delete all data from the database.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction onClick={handleResetDB}>Continue</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="flex gap-4">
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button
|
||||
variant="destructive"
|
||||
disabled={isResetting || isImporting || isUpdating || isResettingMetrics}
|
||||
>
|
||||
Reset Database
|
||||
</Button>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
This action cannot be undone. This will permanently delete all data from the database.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction onClick={handleResetDB}>Continue</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button
|
||||
variant="secondary"
|
||||
disabled={isResetting || isImporting || isUpdating || isResettingMetrics}
|
||||
>
|
||||
Reset Metrics Only
|
||||
</Button>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Reset metrics tables?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
This will clear all metrics tables while preserving your core data (products, orders, etc.).
|
||||
You can then recalculate metrics with the Import Data function.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction onClick={handleResetMetrics}>Continue</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</div>
|
||||
|
||||
{resetProgress && (
|
||||
<div className="mt-4">
|
||||
<Progress value={Number(resetProgress.percentage)} className="mb-2" />
|
||||
@@ -752,84 +812,63 @@ export function Settings() {
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Test Data Snapshots Card */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Test Data Snapshots</CardTitle>
|
||||
<CardDescription>Create and restore test data snapshots for development and testing.</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="flex space-x-4">
|
||||
<Button
|
||||
onClick={handleCreateSnapshot}
|
||||
disabled={isCreatingSnapshot || isRestoringSnapshot || isImporting || isUpdating || isResetting}
|
||||
>
|
||||
{isCreatingSnapshot ? (
|
||||
<>
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
Creating Snapshot...
|
||||
</>
|
||||
) : (
|
||||
<>Create Snapshot</>
|
||||
)}
|
||||
</Button>
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button
|
||||
variant="secondary"
|
||||
disabled={isCreatingSnapshot || isRestoringSnapshot || isImporting || isUpdating || isResetting}
|
||||
>
|
||||
{isRestoringSnapshot ? (
|
||||
<>
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
Restoring...
|
||||
</>
|
||||
) : (
|
||||
<>Restore Snapshot</>
|
||||
)}
|
||||
</Button>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Restore test data snapshot?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
This will replace your current database with the test data snapshot. Any unsaved changes will be lost.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction onClick={handleRestoreSnapshot}>Continue</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</div>
|
||||
{snapshotProgress && (
|
||||
<div>
|
||||
<Progress value={Number(snapshotProgress.percentage)} className="mb-2" />
|
||||
{resetMetricsProgress && (
|
||||
<div className="mt-4">
|
||||
<Progress value={Number(resetMetricsProgress.percentage)} className="mb-2" />
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{snapshotProgress.message || 'Processing snapshot...'}
|
||||
{resetMetricsProgress.message || 'Resetting metrics...'}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
<div className="text-sm text-muted-foreground">
|
||||
<p>The test data snapshot includes:</p>
|
||||
<ul className="list-disc list-inside mt-2">
|
||||
<li>~100 diverse products with associated data</li>
|
||||
<li>Orders from the last 6 months</li>
|
||||
<li>Purchase orders from the last 6 months</li>
|
||||
<li>Categories and product relationships</li>
|
||||
</ul>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Show progress outside cards if neither operation is running but we have progress state */}
|
||||
{!isUpdating && !isImporting && !isResetting && (updateProgress || importProgress || resetProgress) && (
|
||||
{/* Add new Metrics Calculation Card */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Metrics Calculation</CardTitle>
|
||||
<CardDescription>Calculate metrics for all products based on current data</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
className="flex-1"
|
||||
onClick={handleCalculateMetrics}
|
||||
disabled={isCalculatingMetrics || isImporting || isUpdating || isResetting || isResettingMetrics}
|
||||
>
|
||||
{isCalculatingMetrics ? (
|
||||
<>
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
Calculating Metrics...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<RefreshCw className="mr-2 h-4 w-4" />
|
||||
Calculate Metrics
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
|
||||
{isCalculatingMetrics && (
|
||||
<Button
|
||||
variant="destructive"
|
||||
onClick={handleCancel}
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{metricsProgress && renderProgress(metricsProgress)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Show progress outside cards if no operation is running but we have progress state */}
|
||||
{!isUpdating && !isImporting && !isResetting && !isResettingMetrics && !isCalculatingMetrics &&
|
||||
(updateProgress || importProgress || resetProgress || resetMetricsProgress || metricsProgress) && (
|
||||
<>
|
||||
{renderProgress(updateProgress || importProgress || resetProgress)}
|
||||
{renderProgress(updateProgress || importProgress || resetProgress || resetMetricsProgress || metricsProgress)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user