Implemented Rollback Functionality

This commit is contained in:
2025-08-06 15:18:44 -05:00
parent d741dd5466
commit 78818793f2
20 changed files with 6365 additions and 74 deletions

View File

@@ -95,6 +95,26 @@ class Logger {
await this.progressService.logOperationStart(config);
}
/**
* Logs rollback operation start with configuration details (Requirements 3.1, 7.1, 8.3)
* @param {Object} config - Configuration object
* @returns {Promise<void>}
*/
async logRollbackStart(config) {
await this.info(`Starting price rollback operation with configuration:`);
await this.info(` Target Tag: ${config.targetTag}`);
await this.info(` Operation Mode: rollback`);
await this.info(` Shop Domain: ${config.shopDomain}`);
// Also log to progress file with rollback-specific format
try {
await this.progressService.logRollbackStart(config);
} catch (error) {
// Progress logging should not block main operations
console.warn(`Warning: Failed to log to progress file: ${error.message}`);
}
}
/**
* Logs product count information (Requirement 3.2)
* @param {number} count - Number of matching products found
@@ -128,6 +148,30 @@ class Logger {
await this.progressService.logProductUpdate(entry);
}
/**
* Logs successful rollback operations (Requirements 3.3, 7.2, 8.3)
* @param {Object} entry - Rollback update entry
* @param {string} entry.productTitle - Product title
* @param {string} entry.productId - Product ID
* @param {string} entry.variantId - Variant ID
* @param {number} entry.oldPrice - Original price before rollback
* @param {number} entry.compareAtPrice - Compare-at price being used as new price
* @param {number} entry.newPrice - New price (same as compare-at price)
* @returns {Promise<void>}
*/
async logRollbackUpdate(entry) {
const message = `${this.colors.green}🔄${this.colors.reset} Rolled back "${entry.productTitle}" - Price: ${entry.oldPrice}${entry.newPrice} (from Compare At: ${entry.compareAtPrice})`;
console.log(message);
// Also log to progress file with rollback-specific format
try {
await this.progressService.logRollbackUpdate(entry);
} catch (error) {
// Progress logging should not block main operations
console.warn(`Warning: Failed to log to progress file: ${error.message}`);
}
}
/**
* Logs completion summary (Requirement 3.4)
* @param {Object} summary - Summary statistics
@@ -163,6 +207,59 @@ class Logger {
await this.progressService.logCompletionSummary(summary);
}
/**
* Logs rollback completion summary (Requirements 3.5, 7.3, 8.3)
* @param {Object} summary - Rollback summary statistics
* @param {number} summary.totalProducts - Total products processed
* @param {number} summary.totalVariants - Total variants processed
* @param {number} summary.eligibleVariants - Variants eligible for rollback
* @param {number} summary.successfulRollbacks - Successful rollback operations
* @param {number} summary.failedRollbacks - Failed rollback operations
* @param {number} summary.skippedVariants - Variants skipped (no compare-at price)
* @param {Date} summary.startTime - Operation start time
* @returns {Promise<void>}
*/
async logRollbackSummary(summary) {
await this.info("=".repeat(50));
await this.info("ROLLBACK OPERATION COMPLETE");
await this.info("=".repeat(50));
await this.info(`Total Products Processed: ${summary.totalProducts}`);
await this.info(`Total Variants Processed: ${summary.totalVariants}`);
await this.info(`Eligible Variants: ${summary.eligibleVariants}`);
await this.info(
`Successful Rollbacks: ${this.colors.green}${summary.successfulRollbacks}${this.colors.reset}`
);
if (summary.failedRollbacks > 0) {
await this.info(
`Failed Rollbacks: ${this.colors.red}${summary.failedRollbacks}${this.colors.reset}`
);
} else {
await this.info(`Failed Rollbacks: ${summary.failedRollbacks}`);
}
if (summary.skippedVariants > 0) {
await this.info(
`Skipped Variants: ${this.colors.yellow}${summary.skippedVariants}${this.colors.reset} (no compare-at price)`
);
} else {
await this.info(`Skipped Variants: ${summary.skippedVariants}`);
}
if (summary.startTime) {
const duration = Math.round((new Date() - summary.startTime) / 1000);
await this.info(`Duration: ${duration} seconds`);
}
// Also log to progress file with rollback-specific format
try {
await this.progressService.logRollbackSummary(summary);
} catch (error) {
// Progress logging should not block main operations
console.warn(`Warning: Failed to log to progress file: ${error.message}`);
}
}
/**
* Logs error details and continues processing (Requirement 3.5)
* @param {Object} entry - Error entry
@@ -226,12 +323,18 @@ class Logger {
return;
}
const operationType =
summary.successfulRollbacks !== undefined ? "ROLLBACK" : "UPDATE";
await this.info("=".repeat(50));
await this.info("ERROR ANALYSIS");
await this.info(`${operationType} ERROR ANALYSIS`);
await this.info("=".repeat(50));
// Categorize errors
// Enhanced categorization for rollback operations
const categories = {};
const retryableErrors = [];
const nonRetryableErrors = [];
errors.forEach((error) => {
const category = this.categorizeError(
error.errorMessage || error.error || "Unknown"
@@ -240,6 +343,13 @@ class Logger {
categories[category] = [];
}
categories[category].push(error);
// Track retryable vs non-retryable errors for rollback analysis
if (error.retryable === true) {
retryableErrors.push(error);
} else if (error.retryable === false) {
nonRetryableErrors.push(error);
}
});
// Display category breakdown
@@ -254,9 +364,52 @@ class Logger {
);
});
// Rollback-specific error analysis (Requirements 4.3, 4.5)
if (operationType === "ROLLBACK") {
await this.info("\nRollback Error Analysis:");
if (retryableErrors.length > 0) {
await this.info(
` Retryable Errors: ${retryableErrors.length} (${(
(retryableErrors.length / errors.length) *
100
).toFixed(1)}%)`
);
}
if (nonRetryableErrors.length > 0) {
await this.info(
` Non-Retryable Errors: ${nonRetryableErrors.length} (${(
(nonRetryableErrors.length / errors.length) *
100
).toFixed(1)}%)`
);
}
// Analyze rollback-specific error patterns
const validationErrors = errors.filter(
(e) =>
e.errorType === "validation_error" || e.errorType === "validation"
);
if (validationErrors.length > 0) {
await this.info(
` Products without compare-at prices: ${validationErrors.length}`
);
}
const networkErrors = errors.filter(
(e) => e.errorType === "network_error"
);
if (networkErrors.length > 0) {
await this.info(
` Network-related failures: ${networkErrors.length} (consider retry)`
);
}
}
// Provide recommendations based on error patterns
await this.info("\nRecommendations:");
await this.provideErrorRecommendations(categories, summary);
await this.provideErrorRecommendations(categories, summary, operationType);
// Log to progress file as well
await this.progressService.logErrorAnalysis(errors);
@@ -327,9 +480,14 @@ class Logger {
* Provide recommendations based on error patterns
* @param {Object} categories - Categorized errors
* @param {Object} summary - Operation summary
* @param {string} operationType - Type of operation ('UPDATE' or 'ROLLBACK')
* @returns {Promise<void>}
*/
async provideErrorRecommendations(categories, summary) {
async provideErrorRecommendations(
categories,
summary,
operationType = "UPDATE"
) {
if (categories["Rate Limiting"]) {
await this.info(
" • Consider reducing batch size or adding delays between requests"
@@ -342,6 +500,11 @@ class Logger {
if (categories["Network Issues"]) {
await this.info(" • Check your internet connection stability");
await this.info(" • Consider running the script during off-peak hours");
if (operationType === "ROLLBACK") {
await this.info(
" • Network errors during rollback are retryable - consider re-running"
);
}
}
if (categories["Authentication"]) {
@@ -352,32 +515,90 @@ class Logger {
}
if (categories["Data Validation"]) {
await this.info(
" • Review product data for invalid prices or missing information"
);
await this.info(
" • Consider adding more robust data validation before updates"
);
if (operationType === "ROLLBACK") {
await this.info(
" • Products without compare-at prices cannot be rolled back"
);
await this.info(
" • Consider filtering products to only include those with compare-at prices"
);
await this.info(
" • Review which products were updated in the original price adjustment"
);
} else {
await this.info(
" • Review product data for invalid prices or missing information"
);
await this.info(
" • Consider adding more robust data validation before updates"
);
}
}
if (categories["Server Errors"]) {
await this.info(" • Shopify may be experiencing temporary issues");
await this.info(" • Try running the script again later");
if (operationType === "ROLLBACK") {
await this.info(" • Server errors during rollback are retryable");
}
}
// Success rate analysis
const successRate =
summary.totalVariants > 0
? ((summary.successfulUpdates / summary.totalVariants) * 100).toFixed(1)
: 0;
// Rollback-specific recommendations (Requirement 4.5)
if (operationType === "ROLLBACK") {
if (categories["Resource Not Found"]) {
await this.info(
" • Some products or variants may have been deleted since the original update"
);
await this.info(
" • Consider checking product existence before rollback operations"
);
}
if (categories["Permissions"]) {
await this.info(
" • Ensure your API credentials have product update permissions"
);
await this.info(
" • Rollback operations require the same permissions as price updates"
);
}
}
// Success rate analysis with operation-specific metrics
let successRate;
if (operationType === "ROLLBACK") {
successRate =
summary.eligibleVariants > 0
? (
(summary.successfulRollbacks / summary.eligibleVariants) *
100
).toFixed(1)
: 0;
} else {
successRate =
summary.totalVariants > 0
? ((summary.successfulUpdates / summary.totalVariants) * 100).toFixed(
1
)
: 0;
}
if (successRate < 50) {
await this.warning(
" • Low success rate detected - consider reviewing configuration"
` • Low success rate detected (${successRate}%) - consider reviewing configuration`
);
if (operationType === "ROLLBACK") {
await this.warning(
" • Many products may not have valid compare-at prices for rollback"
);
}
} else if (successRate < 90) {
await this.info(
" • Moderate success rate - some optimization may be beneficial"
` • Moderate success rate (${successRate}%) - some optimization may be beneficial`
);
} else {
await this.info(
` • Good success rate (${successRate}%) - most operations completed successfully`
);
}
}