upskill-event-manager/assets/js/zoho-admin.js
ben 03b9bce52d fix(zoho): Fix silent sync failures with API response validation and hash reset
Zoho CRM sync appeared connected but silently failed to write data due to
unvalidated API responses. Sync methods now validate Zoho responses before
updating hashes, ensuring failed records re-sync on next run. Also fixes
staging detection to use wp_parse_url hostname parsing instead of fragile
strpos matching, adds admin UI for resetting sync hashes, and bumps
HVAC_PLUGIN_VERSION to 2.2.11 to bust browser cache for updated JS.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 11:25:26 -04:00

754 lines
No EOL
33 KiB
JavaScript

/**
* Zoho CRM Admin JavaScript
*
* @package HVACCommunityEvents
*/
jQuery(document).ready(function ($) {
// =====================================================
// Password visibility toggle
// =====================================================
$('#toggle-secret').on('click', function () {
var passwordField = $('#zoho_client_secret');
var toggleBtn = $(this);
if (passwordField.attr('type') === 'password') {
passwordField.attr('type', 'text');
toggleBtn.text('Hide');
} else {
passwordField.attr('type', 'password');
toggleBtn.text('Show');
}
});
// =====================================================
// Copy redirect URI to clipboard
// =====================================================
$('#copy-redirect-uri').on('click', function () {
var redirectUri = hvacZoho.redirectUri || '';
if (!redirectUri) {
alert('Redirect URI not available');
return;
}
navigator.clipboard.writeText(redirectUri).then(function () {
$('#copy-redirect-uri').text('Copied!').prop('disabled', true);
setTimeout(function () {
$('#copy-redirect-uri').text('Copy').prop('disabled', false);
}, 2000);
}).catch(function () {
// Fallback for older browsers
var tempInput = $('<input>');
$('body').append(tempInput);
tempInput.val(redirectUri).select();
document.execCommand('copy');
tempInput.remove();
$('#copy-redirect-uri').text('Copied!').prop('disabled', true);
setTimeout(function () {
$('#copy-redirect-uri').text('Copy').prop('disabled', false);
}, 2000);
});
});
// =====================================================
// Flush rewrite rules
// =====================================================
$('#flush-rewrite-rules').on('click', function () {
var button = $(this);
button.prop('disabled', true).text('Flushing...');
$.post(hvacZoho.ajaxUrl, {
action: 'hvac_zoho_flush_rewrite_rules'
}, function (response) {
if (response.success) {
button.text('Flushed!').css('color', '#46b450');
setTimeout(function () {
location.reload();
}, 1000);
} else {
button.text('Error').css('color', '#dc3232');
setTimeout(function () {
button.prop('disabled', false).text('Flush Rules').css('color', '');
}, 2000);
}
});
});
// =====================================================
// Credentials form submission
// =====================================================
// =====================================================
// Credentials form submission
// =====================================================
$('#zoho-credentials-form').on('submit', function (e) {
e.preventDefault();
var formData = {
action: 'hvac_zoho_save_credentials',
zoho_client_id: $('#zoho_client_id').val(),
zoho_client_secret: $('#zoho_client_secret').val(),
nonce: $('input[name="hvac_zoho_nonce"]').val()
};
var $saveBtn = $('#save-credentials');
$saveBtn.prop('disabled', true).text('Saving...');
// Clear any previous messages
$('.notice').remove();
$.ajax({
url: hvacZoho.ajaxUrl,
method: 'POST',
data: formData,
success: function (response) {
if (response.success) {
window.location.href = window.location.href.split('?')[0] + '?page=hvac-zoho-sync&credentials_saved=1';
} else {
// Create error notice
var errorHtml = '<div class="notice notice-error is-dismissible"><p>' +
'<strong>Error saving credentials:</strong> ' + response.data.message +
'</p></div>';
$('h1').after(errorHtml);
$saveBtn.prop('disabled', false).text('Save Credentials');
}
},
error: function (xhr, status, error) {
console.error('Zoho Save Error:', xhr);
var errorMessage = 'Unknown error occurred';
var errorDetails = '';
if (xhr.status === 400 || xhr.status === 403) {
errorMessage = 'Request blocked (' + xhr.status + ')';
errorDetails = 'This is likely due to a security plugin or Web Application Firewall (WAF) blocking the request. ' +
'The content (e.g. Client Secret) might be triggering a false positive security rule.';
} else if (xhr.status === 500) {
errorMessage = 'Server Error (500)';
errorDetails = 'Check the server error logs for more information.';
} else if (status === 'timeout') {
errorMessage = 'Request timed out';
} else if (xhr.responseJSON && xhr.responseJSON.data && xhr.responseJSON.data.message) {
errorMessage = xhr.responseJSON.data.message;
} else {
errorMessage = error || status;
}
var errorHtml = '<div class="notice notice-error is-dismissible" style="border-left-color: #dc3232;">' +
'<p><strong>❌ Save Failed:</strong> ' + errorMessage + '</p>';
if (errorDetails) {
errorHtml += '<p><em>' + errorDetails + '</em></p>';
}
// Add debug details
errorHtml += '<details style="margin-top: 10px;">' +
'<summary>Technical Details</summary>' +
'<ul style="margin-top: 5px; font-size: 12px; background: #f0f0f0; padding: 10px;">' +
'<li>Status: ' + xhr.status + ' ' + xhr.statusText + '</li>' +
'<li>Response: ' + (xhr.responseText ? xhr.responseText.substring(0, 100) + '...' : '(empty)') + '</li>' +
'</ul>' +
'</details></div>';
$('h1').after(errorHtml);
// Re-enable button
$saveBtn.prop('disabled', false).text('Save Credentials');
// Scroll to error
$('html, body').animate({ scrollTop: 0 }, 'slow');
}
});
});
// =====================================================
// OAuth authorization handler
// =====================================================
$('#start-oauth').on('click', function () {
var clientId = $('#zoho_client_id').val();
var clientSecret = $('#zoho_client_secret').val();
if (!clientId || !clientSecret) {
alert('Please save your credentials first before starting OAuth authorization.');
return;
}
// Use server-generated OAuth URL with CSRF state parameter
var oauthUrl = hvacZoho.oauthUrl || '';
if (!oauthUrl) {
alert('OAuth URL not available. Please save your credentials first and refresh the page.');
return;
}
// Open OAuth URL in the same window to handle callback properly
window.location.href = oauthUrl;
});
// =====================================================
// Test connection
// =====================================================
$('#test-connection').on('click', function () {
var $button = $(this);
var $status = $('#connection-status');
$button.prop('disabled', true).text('Testing...');
$status.html('');
$.ajax({
url: hvacZoho.ajaxUrl,
method: 'POST',
data: {
action: 'hvac_zoho_test_connection',
nonce: hvacZoho.nonce
},
success: function (response) {
if (response.success) {
var successHtml = '<div class="notice notice-success">';
successHtml += '<p><strong>' + response.data.message + '</strong></p>';
// Show credential details
if (response.data.client_id) {
successHtml += '<p>Client ID: ' + response.data.client_id + '</p>';
}
if (response.data.client_secret_exists) {
successHtml += '<p>Client Secret: ✓ Loaded</p>';
}
if (response.data.refresh_token_exists) {
successHtml += '<p>Refresh Token: ✓ Found</p>';
} else {
successHtml += '<p>Refresh Token: ❌ Missing (OAuth required)</p>';
}
// Show staging warning if present
if (response.data.staging_warning) {
successHtml += '<div style="margin-top: 10px; padding: 8px 12px; background: #fff8e5; border-left: 3px solid #ffb900;">';
successHtml += '<p style="margin: 0; color: #826200;"><strong>Warning:</strong> ' + response.data.staging_warning + '</p>';
successHtml += '</div>';
}
// Show debug info if available
if (response.data.debug) {
successHtml += '<details style="margin-top: 10px;">';
successHtml += '<summary>Debug Information</summary>';
successHtml += '<pre style="background: #f0f0f0; padding: 10px; font-size: 12px;">';
successHtml += JSON.stringify(response.data.debug, null, 2);
successHtml += '</pre></details>';
}
successHtml += '</div>';
$status.html(successHtml);
} else {
// Debug: Log the response to see what we're getting
console.log('Error response:', response);
console.log('Message:', response.data.message);
console.log('Has auth_url:', !!response.data.auth_url);
// Handle OAuth authorization case specially
if (response.data.message === 'OAuth Authorization Required' && response.data.auth_url) {
var authHtml = '<div class="notice notice-warning">';
authHtml += '<h3>🔐 OAuth Authorization Required</h3>';
authHtml += '<p><strong>' + response.data.details + '</strong></p>';
if (response.data.next_steps) {
authHtml += '<ol>';
response.data.next_steps.forEach(function (step) {
authHtml += '<li>' + step + '</li>';
});
authHtml += '</ol>';
}
authHtml += '<p><a href="' + response.data.auth_url + '" target="_blank" class="button button-primary" style="margin: 10px 0;">🚀 Authorize with Zoho CRM</a></p>';
// Show credential status
if (response.data.credentials_status) {
authHtml += '<p><strong>Current Status:</strong></p>';
authHtml += '<ul>';
authHtml += '<li>Client ID: ' + response.data.credentials_status.client_id + '</li>';
authHtml += '<li>Client Secret: ' + (response.data.credentials_status.client_secret_exists ? '✓ Loaded' : '❌ Missing') + '</li>';
authHtml += '<li>Refresh Token: ' + (response.data.credentials_status.refresh_token_exists ? '✓ Found' : '❌ Missing') + '</li>';
authHtml += '</ul>';
}
authHtml += '<p><em>After authorization, come back and test the connection again.</em></p>';
authHtml += '</div>';
$status.html(authHtml);
return;
} else {
console.log('OAuth conditions not met:');
console.log('Message matches:', response.data.message === 'OAuth Authorization Required');
console.log('Auth URL exists:', !!response.data.auth_url);
}
// Create detailed error display for other errors
var errorHtml = '<div class="notice notice-error">';
errorHtml += '<p><strong>' + response.data.message + ':</strong> ' + response.data.error + '</p>';
// Add error code if available
if (response.data.code) {
errorHtml += '<p><strong>Error Code:</strong> ' + response.data.code + '</p>';
}
// Add details if available
if (response.data.details) {
errorHtml += '<p><strong>Details:</strong> ' + response.data.details + '</p>';
}
// Add debugging info
errorHtml += '<div class="hvac-zoho-debug-info">';
errorHtml += '<p><strong>Debug Information:</strong></p>';
errorHtml += '<p>Check the PHP error log for more details.</p>';
errorHtml += '<p>Log location: wp-content/plugins/hvac-community-events/logs/zoho-debug.log</p>';
// Add raw response data if available
if (response.data.raw) {
errorHtml += '<details>';
errorHtml += '<summary>Raw Response Data (click to expand)</summary>';
errorHtml += '<pre style="background: #f0f0f0; padding: 10px; max-height: 300px; overflow: auto;">' +
JSON.stringify(JSON.parse(response.data.raw), null, 2) +
'</pre>';
errorHtml += '</details>';
}
// Add file/line info if available (for exceptions)
if (response.data.file) {
errorHtml += '<p><strong>File:</strong> ' + response.data.file + '</p>';
}
// Add trace if available
if (response.data.trace) {
errorHtml += '<details>';
errorHtml += '<summary>Stack Trace (click to expand)</summary>';
errorHtml += '<pre style="background: #f0f0f0; padding: 10px; max-height: 300px; overflow: auto;">' +
response.data.trace +
'</pre>';
errorHtml += '</details>';
}
errorHtml += '</div>'; // Close debug info
errorHtml += '</div>'; // Close notice
$status.html(errorHtml);
}
},
error: function (xhr, status, error) {
var errorHtml = '<div class="notice notice-error">';
errorHtml += '<p><strong>AJAX Error:</strong> Connection test failed</p>';
errorHtml += '<p><strong>Status:</strong> ' + status + '</p>';
errorHtml += '<p><strong>Error:</strong> ' + error + '</p>';
errorHtml += '</div>';
$status.html(errorHtml);
},
complete: function () {
$button.prop('disabled', false).text('Test Connection');
}
});
});
// =====================================================
// Sync data with batch progress
// =====================================================
/**
* Sync with progress - auto-continues through all batches
* @param {jQuery} $button - The sync button
* @param {string} type - Sync type (events, users, attendees, rsvps, purchases)
* @param {jQuery} $status - Status container element
* @param {number} offset - Current offset
* @param {object} accumulated - Accumulated results across batches
*/
function syncWithProgress($button, type, $status, offset, accumulated) {
accumulated = accumulated || {
synced: 0,
failed: 0,
errors: [],
total: 0,
staging_mode: false,
responses: [],
test_data: []
};
$.ajax({
url: hvacZoho.ajaxUrl,
method: 'POST',
data: {
action: 'hvac_zoho_sync_data',
type: type,
offset: offset,
nonce: hvacZoho.nonce
},
success: function (response) {
if (response.success) {
var result = response.data;
// Update accumulated totals
accumulated.total = result.total; // Total is consistent across batches
accumulated.synced += result.synced;
accumulated.failed += result.failed;
accumulated.staging_mode = result.staging_mode;
// Merge arrays
if (result.errors && result.errors.length > 0) {
accumulated.errors = accumulated.errors.concat(result.errors);
}
if (result.responses && result.responses.length > 0) {
accumulated.responses = accumulated.responses.concat(result.responses);
}
if (result.test_data && result.test_data.length > 0) {
accumulated.test_data = accumulated.test_data.concat(result.test_data);
}
// Calculate progress
var processed = accumulated.synced + accumulated.failed;
var percent = accumulated.total > 0 ? Math.round((processed / accumulated.total) * 100) : 0;
// Update progress bar
var progressHtml = '<div class="sync-progress-bar" style="margin: 10px 0;">' +
'<div style="background: #e0e0e0; border-radius: 4px; overflow: hidden; height: 20px;">' +
'<div style="background: linear-gradient(90deg, #0073aa, #00a0d2); height: 100%; width: ' + percent + '%; transition: width 0.3s;"></div>' +
'</div>' +
'<p style="margin: 5px 0; font-size: 13px;">' +
'<strong>' + processed + ' of ' + accumulated.total + '</strong> processed (' + percent + '%)' +
'</p></div>';
$status.html(progressHtml);
// Check if there are more batches
if (result.has_more && result.next_offset > offset) {
// Continue with next batch
syncWithProgress($button, type, $status, result.next_offset, accumulated);
} else {
// All done! Show final results
displaySyncResults($button, type, $status, accumulated, result);
}
} else {
$status.html('<div class="notice notice-error"><p>' + response.data.message + ': ' + response.data.error + '</p></div>');
$button.prop('disabled', false).text('Sync ' + type.charAt(0).toUpperCase() + type.slice(1));
}
},
error: function () {
$status.html('<div class="notice notice-error"><p>Sync failed - network or server error</p></div>');
$button.prop('disabled', false).text('Sync ' + type.charAt(0).toUpperCase() + type.slice(1));
}
});
}
/**
* Display final sync results
*/
function displaySyncResults($button, type, $status, accumulated, lastResult) {
var html = '<div class="notice notice-success">';
if (accumulated.staging_mode) {
html += '<h4>🔧 STAGING MODE - Simulation Complete</h4>';
html += '<p>No data was sent to Zoho CRM. This is a dry-run showing what would sync.</p>';
} else {
html += '<p><strong>✅ Sync completed successfully!</strong></p>';
}
if (lastResult.version) {
html += '<p><strong>Server Code Version:</strong> ' + lastResult.version + '</p>';
}
html += '<ul>' +
'<li>Total records: ' + accumulated.total + '</li>' +
'<li>Synced: ' + accumulated.synced + '</li>' +
'<li>Failed: ' + accumulated.failed + '</li>' +
'</ul>';
// Show test data for staging
if (accumulated.test_data && accumulated.test_data.length > 0) {
html += '<details>' +
'<summary>View test data (first 5 records)</summary>' +
'<pre style="background: #f0f0f0; padding: 10px; overflow: auto; max-height: 300px;">' +
JSON.stringify(accumulated.test_data.slice(0, 5), null, 2) +
'</pre>' +
'</details>';
}
// Debug info
if (lastResult.debug_info) {
html += '<details style="margin-top: 10px;">' +
'<summary>🔍 Debug: Mode Detection Info</summary>' +
'<div style="background: #f0f0f0; padding: 10px; font-size: 12px; margin-top: 5px;">';
if (typeof lastResult.debug_info.is_staging !== 'undefined') {
html += '<p><strong>Is Staging:</strong> ' + (lastResult.debug_info.is_staging ? '✅ YES' : '❌ NO') + '</p>';
}
if (lastResult.debug_info.site_url) {
html += '<p><strong>Site URL:</strong> ' + lastResult.debug_info.site_url + '</p>';
}
html += '<pre style="background: #e0e0e0; padding: 5px; margin-top: 5px; max-height: 150px; overflow: auto;">' +
JSON.stringify(lastResult.debug_info, null, 2) +
'</pre>' +
'</div>' +
'</details>';
}
// Show errors if any
if (accumulated.errors && accumulated.errors.length > 0) {
html += '<details style="margin-top: 10px; border: 2px solid #dc3232;">' +
'<summary style="font-weight: bold; color: #dc3232;">❌ Errors (' + accumulated.errors.length + ')</summary>' +
'<pre style="background: #fff0f0; padding: 10px; overflow: auto; max-height: 300px;">' +
JSON.stringify(accumulated.errors.slice(0, 20), null, 2) +
'</pre>' +
'</details>';
}
// Show API responses preview
if (accumulated.responses && accumulated.responses.length > 0) {
html += '<details style="margin-top: 10px;">' +
'<summary>📡 Raw API Responses (first 10)</summary>' +
'<pre style="background: #f0f0f0; padding: 10px; overflow: auto; max-height: 200px;">' +
JSON.stringify(accumulated.responses.slice(0, 10), null, 2) +
'</pre>' +
'</details>';
}
html += '</div>';
$status.html(html);
$button.prop('disabled', false).text('Sync ' + type.charAt(0).toUpperCase() + type.slice(1));
}
// Sync button click handler
$('.sync-button').on('click', function () {
var $button = $(this);
var type = $button.data('type');
var $status = $('#' + type + '-status');
$button.prop('disabled', true).text('Syncing...');
$status.html('<p>Starting sync for ' + type + '...</p>');
// Start sync with batch progress
syncWithProgress($button, type, $status, 0, null);
});
// Save settings
$('#zoho-settings-form').on('submit', function (e) {
e.preventDefault();
var $form = $(this);
var $button = $form.find('button[type="submit"]');
$button.prop('disabled', true).text('Saving...');
$.ajax({
url: hvacZoho.ajaxUrl,
method: 'POST',
data: {
action: 'hvac_zoho_save_settings',
nonce: hvacZoho.nonce,
auto_sync: $form.find('input[name="auto_sync"]').is(':checked') ? '1' : '0',
sync_frequency: $form.find('select[name="sync_frequency"]').val()
},
success: function (response) {
if (response.success) {
// Reload page to show updated status
window.location.reload();
} else {
// Use toast notification instead of alert
if (window.HVACToast) {
HVACToast.error('Error saving settings: ' + response.data.message);
} else {
alert('Error saving settings: ' + response.data.message);
}
$button.prop('disabled', false).text('Save Settings');
}
},
error: function () {
// Use toast notification instead of alert
if (window.HVACToast) {
HVACToast.error('Error saving settings');
} else {
alert('Error saving settings');
}
$button.prop('disabled', false).text('Save Settings');
}
});
});
// =====================================================
// Run Scheduled Sync Now Handler
// =====================================================
$('#run-scheduled-sync-now').on('click', function () {
var $button = $(this);
var $status = $('#scheduled-sync-status');
$button.prop('disabled', true).text('Running...');
$status.html('<p>Starting scheduled sync...</p>');
$.ajax({
url: hvacZoho.ajaxUrl,
method: 'POST',
data: {
action: 'hvac_zoho_run_scheduled_sync',
nonce: hvacZoho.nonce
},
success: function (response) {
if (response.success) {
var result = response.data.result;
var html = '<div class="notice notice-success">';
if (result.events && result.events.staging_mode) {
html += '<h4>🔧 STAGING MODE - Simulation Complete</h4>';
html += '<p>No data was sent to Zoho CRM.</p>';
} else {
html += '<p><strong>✅ Scheduled sync completed!</strong></p>';
}
html += '<ul>';
html += '<li>Total synced: ' + (result.total_synced || 0) + '</li>';
html += '<li>Total failed: ' + (result.total_failed || 0) + '</li>';
html += '<li>Duration: ' + (result.duration_seconds || 0) + ' seconds</li>';
html += '</ul>';
// Show details per type
html += '<details><summary>Details by type</summary><ul>';
['events', 'users', 'attendees', 'rsvps', 'purchases'].forEach(function (type) {
if (result[type]) {
html += '<li><strong>' + type + ':</strong> ' +
(result[type].synced || 0) + ' synced, ' +
(result[type].failed || 0) + ' failed</li>';
}
});
html += '</ul></details>';
html += '</div>';
$status.html(html);
} else {
$status.html('<div class="notice notice-error"><p>Sync failed: ' + response.data.message + '</p></div>');
}
},
error: function () {
$status.html('<div class="notice notice-error"><p>Error running scheduled sync</p></div>');
},
complete: function () {
$button.prop('disabled', false).text('🔄 Run Sync Now');
}
});
});
// =====================================================
// Reset Sync Hashes Handler
// =====================================================
$('#reset-sync-hashes').on('click', function () {
if (!confirm('This will clear all sync hashes and force every record to re-sync on the next run. Continue?')) {
return;
}
var $button = $(this);
var $status = $('#reset-hashes-status');
$button.prop('disabled', true).text('Resetting...');
$status.text('');
$.ajax({
url: hvacZoho.ajaxUrl,
method: 'POST',
data: {
action: 'hvac_zoho_reset_sync_hashes',
nonce: hvacZoho.nonce
},
success: function (response) {
if (response.success) {
$status.html(
'<span style="color: #46b450;">' +
response.data.posts_cleared + ' post hashes and ' +
response.data.users_cleared + ' user hashes cleared.</span>'
);
} else {
$status.html('<span style="color: #dc3232;">Error: ' + response.data.message + '</span>');
}
},
error: function () {
$status.html('<span style="color: #dc3232;">Network error - please try again</span>');
},
complete: function () {
$button.prop('disabled', false).text('Force Full Re-sync (Reset Hashes)');
}
});
});
// =====================================================
// Diagnostic Test Handler
// =====================================================
$('#diagnostic-test').on('click', function () {
var $button = $(this);
$button.prop('disabled', true).text('Testing...');
// Remove existing notices
$('.notice').remove();
// Test 1: Simple GET
var runSimpleTest = function () {
return $.ajax({
url: hvacZoho.ajaxUrl,
method: 'POST',
data: {
action: 'hvac_zoho_simple_test'
}
});
};
// Test 2: Payload Test (simulates credentials)
var runPayloadTest = function () {
var fakeId = '1000.' + new Array(20).join('a');
var fakeSecret = new Array(30).join('b');
return $.ajax({
url: hvacZoho.ajaxUrl,
method: 'POST',
data: {
action: 'hvac_zoho_simple_test',
test_payload: 'SIMULATED_CREDENTIALS',
zoho_client_id: fakeId,
zoho_client_secret: fakeSecret
}
});
};
// Execute tests sequence
runSimpleTest()
.then(function (response) {
if (response.success) {
console.log('Simple test passed');
return runPayloadTest();
} else {
return $.Deferred().reject({ status: 200, statusText: 'OK', responseJSON: response });
}
})
.then(function (response) {
if (response.success) {
// Success!
var successHtml = '<div class="notice notice-success is-dismissible">' +
'<p><strong>✅ Diagnostic Test Passed</strong></p>' +
'<p>AJAX requests are working correctly. No WAF blocking detected for credential-like data.</p>' +
'</div>';
$('h1').after(successHtml);
} else {
return $.Deferred().reject({ status: 200, statusText: 'OK', responseJSON: response });
}
})
.fail(function (xhr) {
var errorHtml = '<div class="notice notice-error is-dismissible">' +
'<p><strong>❌ Diagnostic Test Failed</strong></p>';
if (xhr.status === 400 || xhr.status === 403) {
errorHtml += '<p><strong>WAF Blocking Detected!</strong></p>';
errorHtml += '<p>The server returned ' + xhr.status + ' when sending data.</p>';
} else {
errorHtml += '<p>Status: ' + (xhr.status || 'Unknown') + '</p>';
if (xhr.responseJSON && xhr.responseJSON.data && xhr.responseJSON.data.message) {
errorHtml += '<p>Message: ' + xhr.responseJSON.data.message + '</p>';
}
}
errorHtml += '</div>';
$('h1').after(errorHtml);
})
.always(function () {
$button.prop('disabled', false).text('🏥 Run Diagnostic Test');
});
});
});