Implemented Rollback Functionality
This commit is contained in:
@@ -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`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user