🔧 Bug Fixes: - Fixed product image structure to match Miravia API requirements - Updated MiraviaProduct.php getData() method to wrap images in {"Image": [...]} format - Updated MiraviaCombination.php getData() method to wrap SKU images properly - Resolved error "[4224] The Main image of the product is required" 📋 Changes: - Modified getData() methods to transform flat image arrays to nested structure - Product images: images[] → Images: {"Image": [...]} - SKU images: images[] → Images: {"Image": [...]} - Maintains backward compatibility for empty image arrays 🎯 Impact: - Product uploads will now pass Miravia's image validation - Both product-level and SKU-level images properly formatted - Complies with official Miravia API documentation structure 🤖 Generated with Claude Code (https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
392 lines
14 KiB
PHP
392 lines
14 KiB
PHP
<?php
|
||
if ( ! defined( 'ABSPATH' ) ) { exit; }
|
||
global $wpdb;
|
||
|
||
// Get page and status filter
|
||
$current_page = max(1, intval($_GET['paged'] ?? 1));
|
||
$status_filter = sanitize_text_field($_GET['status_filter'] ?? '');
|
||
|
||
// Load Feed Manager for operations
|
||
require_once MIRAVIA_CLASSES_PATH . 'class.feed-manager.php';
|
||
$feedManager = new MiraviaFeedManager();
|
||
|
||
// Get jobs data
|
||
$limit = 20;
|
||
$offset = ($current_page - 1) * $limit;
|
||
$jobs = $feedManager->getJobs($limit, $offset, $status_filter ?: null);
|
||
$total_jobs = $feedManager->getJobCount($status_filter ?: null);
|
||
$total_pages = ceil($total_jobs / $limit);
|
||
|
||
// Get status counts for filter tabs
|
||
$status_counts = [
|
||
'all' => $feedManager->getJobCount(),
|
||
'PENDING' => $feedManager->getJobCount('PENDING'),
|
||
'CREATING_DOCUMENT' => $feedManager->getJobCount('CREATING_DOCUMENT'),
|
||
'SUBMITTED' => $feedManager->getJobCount('SUBMITTED'),
|
||
'PROCESSING' => $feedManager->getJobCount('PROCESSING'),
|
||
'COMPLETED' => $feedManager->getJobCount('COMPLETED'),
|
||
'FAILED' => $feedManager->getJobCount('FAILED')
|
||
];
|
||
|
||
?>
|
||
<div class="wrap">
|
||
<h2>Feed Jobs Queue</h2>
|
||
<p class="description">Monitor and manage Feed API job submissions. Jobs are processed asynchronously by Miravia's Feed API.</p>
|
||
|
||
<!-- Status Filter Tabs -->
|
||
<div class="nav-tab-wrapper">
|
||
<a href="?page=miravia_settings&subpage=jobs" class="nav-tab <?php echo empty($status_filter) ? 'nav-tab-active' : ''; ?>">
|
||
All (<?php echo $status_counts['all']; ?>)
|
||
</a>
|
||
<a href="?page=miravia_settings&subpage=jobs&status_filter=PENDING" class="nav-tab <?php echo $status_filter === 'PENDING' ? 'nav-tab-active' : ''; ?>">
|
||
Pending (<?php echo $status_counts['PENDING']; ?>)
|
||
</a>
|
||
<a href="?page=miravia_settings&subpage=jobs&status_filter=SUBMITTED" class="nav-tab <?php echo $status_filter === 'SUBMITTED' ? 'nav-tab-active' : ''; ?>">
|
||
Submitted (<?php echo $status_counts['SUBMITTED']; ?>)
|
||
</a>
|
||
<a href="?page=miravia_settings&subpage=jobs&status_filter=PROCESSING" class="nav-tab <?php echo $status_filter === 'PROCESSING' ? 'nav-tab-active' : ''; ?>">
|
||
Processing (<?php echo $status_counts['PROCESSING']; ?>)
|
||
</a>
|
||
<a href="?page=miravia_settings&subpage=jobs&status_filter=COMPLETED" class="nav-tab <?php echo $status_filter === 'COMPLETED' ? 'nav-tab-active' : ''; ?>">
|
||
Completed (<?php echo $status_counts['COMPLETED']; ?>)
|
||
</a>
|
||
<a href="?page=miravia_settings&subpage=jobs&status_filter=FAILED" class="nav-tab <?php echo $status_filter === 'FAILED' ? 'nav-tab-active' : ''; ?>">
|
||
Failed (<?php echo $status_counts['FAILED']; ?>)
|
||
</a>
|
||
</div>
|
||
|
||
<!-- Jobs Table -->
|
||
<table class="wp-list-table widefat fixed striped">
|
||
<thead>
|
||
<tr>
|
||
<th>Job ID</th>
|
||
<th>Feed ID</th>
|
||
<th>Type</th>
|
||
<th>Status</th>
|
||
<th>Products</th>
|
||
<th>Created</th>
|
||
<th>Processing Time</th>
|
||
<th>Actions</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<?php if (empty($jobs)): ?>
|
||
<tr>
|
||
<td colspan="8" style="text-align: center; padding: 40px;">
|
||
<p><strong>No feed jobs found.</strong></p>
|
||
<p>Submit products using the Feed API from the Products page to see jobs here.</p>
|
||
</td>
|
||
</tr>
|
||
<?php else: ?>
|
||
<?php foreach ($jobs as $job): ?>
|
||
<tr data-job-id="<?php echo $job->id; ?>">
|
||
<td><strong>#<?php echo $job->id; ?></strong></td>
|
||
<td>
|
||
<?php if ($job->feed_id): ?>
|
||
<code><?php echo esc_html(substr($job->feed_id, 0, 12)) . '...'; ?></code>
|
||
<?php else: ?>
|
||
<span class="description">—</span>
|
||
<?php endif; ?>
|
||
</td>
|
||
<td><?php echo esc_html($job->feed_type); ?></td>
|
||
<td>
|
||
<?php
|
||
$status_class = '';
|
||
switch ($job->status) {
|
||
case 'COMPLETED':
|
||
$status_class = 'status-completed';
|
||
break;
|
||
case 'FAILED':
|
||
$status_class = 'status-failed';
|
||
break;
|
||
case 'PROCESSING':
|
||
case 'SUBMITTED':
|
||
$status_class = 'status-processing';
|
||
break;
|
||
default:
|
||
$status_class = 'status-pending';
|
||
}
|
||
?>
|
||
<span class="job-status <?php echo $status_class; ?>" id="status-<?php echo $job->id; ?>">
|
||
<?php echo esc_html($job->status); ?>
|
||
</span>
|
||
</td>
|
||
<td><?php echo intval($job->product_count); ?></td>
|
||
<td><?php echo date('M j, Y H:i', strtotime($job->created)); ?></td>
|
||
<td>
|
||
<?php if ($job->processing_start_time && $job->processing_end_time): ?>
|
||
<?php
|
||
$start = new DateTime($job->processing_start_time);
|
||
$end = new DateTime($job->processing_end_time);
|
||
$duration = $start->diff($end);
|
||
echo $duration->format('%H:%I:%S');
|
||
?>
|
||
<?php elseif ($job->processing_start_time): ?>
|
||
<span class="description">In progress...</span>
|
||
<?php else: ?>
|
||
<span class="description">—</span>
|
||
<?php endif; ?>
|
||
</td>
|
||
<td>
|
||
<?php if ($job->feed_id && in_array($job->status, ['SUBMITTED', 'PROCESSING'])): ?>
|
||
<button type="button" class="button update-status-btn" data-job-id="<?php echo $job->id; ?>">
|
||
Update Status
|
||
</button>
|
||
<?php endif; ?>
|
||
|
||
<?php if ($job->status === 'FAILED'): ?>
|
||
<button type="button" class="button resubmit-job-btn" data-job-id="<?php echo $job->id; ?>">
|
||
Resubmit
|
||
</button>
|
||
<?php endif; ?>
|
||
|
||
<?php if ($job->error_message): ?>
|
||
<button type="button" class="button view-error-btn" data-error="<?php echo esc_attr($job->error_message); ?>">
|
||
View Error
|
||
</button>
|
||
<?php endif; ?>
|
||
|
||
<?php if ($job->result_data): ?>
|
||
<button type="button" class="button view-results-btn" data-results="<?php echo esc_attr($job->result_data); ?>">
|
||
View Results
|
||
</button>
|
||
<?php endif; ?>
|
||
</td>
|
||
</tr>
|
||
<?php endforeach; ?>
|
||
<?php endif; ?>
|
||
</tbody>
|
||
</table>
|
||
|
||
<!-- Pagination -->
|
||
<?php if ($total_pages > 1): ?>
|
||
<div class="tablenav bottom">
|
||
<div class="tablenav-pages">
|
||
<span class="displaying-num"><?php echo $total_jobs; ?> items</span>
|
||
<span class="pagination-links">
|
||
<?php if ($current_page > 1): ?>
|
||
<a class="first-page button" href="?page=miravia_settings&subpage=jobs<?php echo $status_filter ? '&status_filter=' . $status_filter : ''; ?>&paged=1">
|
||
‹‹
|
||
</a>
|
||
<a class="prev-page button" href="?page=miravia_settings&subpage=jobs<?php echo $status_filter ? '&status_filter=' . $status_filter : ''; ?>&paged=<?php echo $current_page - 1; ?>">
|
||
‹
|
||
</a>
|
||
<?php endif; ?>
|
||
|
||
<span class="paging-input">
|
||
<span class="tablenav-paging-text">
|
||
<?php echo $current_page; ?> of <span class="total-pages"><?php echo $total_pages; ?></span>
|
||
</span>
|
||
</span>
|
||
|
||
<?php if ($current_page < $total_pages): ?>
|
||
<a class="next-page button" href="?page=miravia_settings&subpage=jobs<?php echo $status_filter ? '&status_filter=' . $status_filter : ''; ?>&paged=<?php echo $current_page + 1; ?>">
|
||
›
|
||
</a>
|
||
<a class="last-page button" href="?page=miravia_settings&subpage=jobs<?php echo $status_filter ? '&status_filter=' . $status_filter : ''; ?>&paged=<?php echo $total_pages; ?>">
|
||
››
|
||
</a>
|
||
<?php endif; ?>
|
||
</span>
|
||
</div>
|
||
</div>
|
||
<?php endif; ?>
|
||
</div>
|
||
|
||
<!-- Error/Results Modal -->
|
||
<div id="job-modal" style="display: none;">
|
||
<div id="job-modal-content">
|
||
<span id="job-modal-close">×</span>
|
||
<h3 id="job-modal-title">Details</h3>
|
||
<div id="job-modal-body"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<style>
|
||
.job-status {
|
||
padding: 4px 8px;
|
||
border-radius: 3px;
|
||
font-weight: 500;
|
||
font-size: 11px;
|
||
text-transform: uppercase;
|
||
}
|
||
|
||
.status-completed {
|
||
background: #d4edda;
|
||
color: #155724;
|
||
border: 1px solid #c3e6cb;
|
||
}
|
||
|
||
.status-failed {
|
||
background: #f8d7da;
|
||
color: #721c24;
|
||
border: 1px solid #f5c6cb;
|
||
}
|
||
|
||
.status-processing {
|
||
background: #d1ecf1;
|
||
color: #0c5460;
|
||
border: 1px solid #bee5eb;
|
||
}
|
||
|
||
.status-pending {
|
||
background: #fff3cd;
|
||
color: #856404;
|
||
border: 1px solid #ffeaa7;
|
||
}
|
||
|
||
/* Modal styles */
|
||
#job-modal {
|
||
position: fixed;
|
||
z-index: 1000;
|
||
left: 0;
|
||
top: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
background-color: rgba(0,0,0,0.5);
|
||
}
|
||
|
||
#job-modal-content {
|
||
background-color: #fefefe;
|
||
margin: 15% auto;
|
||
padding: 20px;
|
||
border: 1px solid #888;
|
||
width: 80%;
|
||
max-width: 600px;
|
||
border-radius: 5px;
|
||
}
|
||
|
||
#job-modal-close {
|
||
color: #aaa;
|
||
float: right;
|
||
font-size: 28px;
|
||
font-weight: bold;
|
||
cursor: pointer;
|
||
}
|
||
|
||
#job-modal-close:hover {
|
||
color: black;
|
||
}
|
||
|
||
#job-modal-body {
|
||
max-height: 400px;
|
||
overflow-y: auto;
|
||
background: #f9f9f9;
|
||
padding: 15px;
|
||
border-radius: 3px;
|
||
font-family: monospace;
|
||
white-space: pre-wrap;
|
||
}
|
||
</style>
|
||
|
||
<script>
|
||
jQuery(document).ready(function($) {
|
||
// Update job status
|
||
$('.update-status-btn').click(function() {
|
||
var button = $(this);
|
||
var jobId = button.data('job-id');
|
||
var statusSpan = $('#status-' + jobId);
|
||
|
||
button.prop('disabled', true).text('Updating...');
|
||
|
||
$.ajax({
|
||
url: ajaxurl,
|
||
type: 'POST',
|
||
data: {
|
||
action: 'miravia_update_job_status',
|
||
job_id: jobId
|
||
},
|
||
success: function(response) {
|
||
if(response.success) {
|
||
// Reload page to show updated status
|
||
location.reload();
|
||
} else {
|
||
alert('Failed to update status: ' + response.data);
|
||
}
|
||
},
|
||
error: function() {
|
||
alert('Failed to update job status');
|
||
},
|
||
complete: function() {
|
||
button.prop('disabled', false).text('Update Status');
|
||
}
|
||
});
|
||
});
|
||
|
||
// Resubmit job
|
||
$('.resubmit-job-btn').click(function() {
|
||
if(!confirm('Are you sure you want to resubmit this job?')) {
|
||
return;
|
||
}
|
||
|
||
var button = $(this);
|
||
var jobId = button.data('job-id');
|
||
|
||
button.prop('disabled', true).text('Resubmitting...');
|
||
|
||
$.ajax({
|
||
url: ajaxurl,
|
||
type: 'POST',
|
||
data: {
|
||
action: 'miravia_resubmit_job',
|
||
job_id: jobId
|
||
},
|
||
success: function(response) {
|
||
if(response.success) {
|
||
alert('Job resubmitted successfully');
|
||
location.reload();
|
||
} else {
|
||
alert('Failed to resubmit job: ' + response.data);
|
||
}
|
||
},
|
||
error: function() {
|
||
alert('Failed to resubmit job');
|
||
},
|
||
complete: function() {
|
||
button.prop('disabled', false).text('Resubmit');
|
||
}
|
||
});
|
||
});
|
||
|
||
// View error details
|
||
$('.view-error-btn').click(function() {
|
||
var error = $(this).data('error');
|
||
$('#job-modal-title').text('Error Details');
|
||
$('#job-modal-body').text(error);
|
||
$('#job-modal').show();
|
||
});
|
||
|
||
// View results
|
||
$('.view-results-btn').click(function() {
|
||
var results = $(this).data('results');
|
||
try {
|
||
var parsedResults = JSON.parse(results);
|
||
$('#job-modal-title').text('Job Results');
|
||
$('#job-modal-body').text(JSON.stringify(parsedResults, null, 2));
|
||
} catch(e) {
|
||
$('#job-modal-body').text(results);
|
||
}
|
||
$('#job-modal').show();
|
||
});
|
||
|
||
// Close modal
|
||
$('#job-modal-close').click(function() {
|
||
$('#job-modal').hide();
|
||
});
|
||
|
||
// Close modal when clicking outside
|
||
$(window).click(function(event) {
|
||
if (event.target.id === 'job-modal') {
|
||
$('#job-modal').hide();
|
||
}
|
||
});
|
||
|
||
// Auto-refresh for active jobs every 30 seconds
|
||
setInterval(function() {
|
||
var hasActiveJobs = $('.status-processing, .status-pending').length > 0;
|
||
if(hasActiveJobs) {
|
||
location.reload();
|
||
}
|
||
}, 30000);
|
||
});
|
||
</script>
|