refactor(js): migrate JavaScript files to module directories
Move 47 JS files from static/{admin,vendor,shared}/js/ to their
respective module directories app/modules/*/static/*/js/:
- Orders: orders.js, order-detail.js
- Catalog: products.js (renamed from vendor-products.js), product-*.js
- Inventory: inventory.js (admin & vendor)
- Customers: customers.js, users.js, user-*.js
- Billing: billing-history.js, subscriptions.js, subscription-tiers.js,
billing.js, invoices.js, feature-store.js, upgrade-prompts.js
- Messaging: messages.js, notifications.js, email-templates.js
- Marketplace: marketplace*.js, letzshop*.js, onboarding.js
- Monitoring: monitoring.js, background-tasks.js, imports.js, logs.js
- Dev Tools: testing-*.js, code-quality-*.js
Update 39 templates to reference new module static paths using
url_for('{module}_static', path='...') pattern.
Files staying in static/ (platform core):
- admin: dashboard, login, platforms, vendors, companies, admin-users,
settings, components, init-alpine, module-config
- vendor: dashboard, login, profile, settings, team, media, init-alpine
- shared: api-client, utils, money, icons, log-config, vendor-selector,
media-picker
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
249
app/modules/dev_tools/static/admin/js/testing-dashboard.js
Normal file
249
app/modules/dev_tools/static/admin/js/testing-dashboard.js
Normal file
@@ -0,0 +1,249 @@
|
||||
// noqa: js-006 - async init pattern is safe, loadData has try/catch
|
||||
/**
|
||||
* Testing Dashboard Component
|
||||
* Manages the pytest testing dashboard page
|
||||
*/
|
||||
|
||||
// Use centralized logger
|
||||
const testingDashboardLog = window.LogConfig.createLogger('TESTING-DASHBOARD');
|
||||
|
||||
function testingDashboard() {
|
||||
return {
|
||||
// Extend base data
|
||||
...data(),
|
||||
|
||||
// Set current page for navigation
|
||||
currentPage: 'testing',
|
||||
|
||||
// Dashboard-specific data
|
||||
loading: false,
|
||||
running: false,
|
||||
collecting: false,
|
||||
error: null,
|
||||
successMessage: null,
|
||||
activeRunId: null,
|
||||
pollInterval: null,
|
||||
elapsedTime: 0,
|
||||
elapsedTimer: null,
|
||||
|
||||
// Statistics
|
||||
stats: {
|
||||
total_tests: 0,
|
||||
passed: 0,
|
||||
failed: 0,
|
||||
errors: 0,
|
||||
skipped: 0,
|
||||
pass_rate: 0,
|
||||
duration_seconds: 0,
|
||||
coverage_percent: null,
|
||||
last_run: null,
|
||||
last_run_status: null,
|
||||
total_test_files: 0,
|
||||
collected_tests: 0,
|
||||
unit_tests: 0,
|
||||
integration_tests: 0,
|
||||
performance_tests: 0,
|
||||
system_tests: 0,
|
||||
last_collected: null,
|
||||
trend: [],
|
||||
by_category: {},
|
||||
top_failing: []
|
||||
},
|
||||
|
||||
// Recent runs
|
||||
runs: [],
|
||||
|
||||
async init() {
|
||||
// Guard against multiple initialization
|
||||
if (window._adminTestingDashboardInitialized) return;
|
||||
window._adminTestingDashboardInitialized = true;
|
||||
|
||||
try {
|
||||
testingDashboardLog.info('Initializing testing dashboard');
|
||||
await this.loadStats();
|
||||
await this.loadRuns();
|
||||
// Check if there's a running test and resume polling
|
||||
await this.checkForRunningTests();
|
||||
} catch (error) {
|
||||
testingDashboardLog.error('Failed to initialize testing dashboard:', error);
|
||||
}
|
||||
},
|
||||
|
||||
async checkForRunningTests() {
|
||||
// Check if there's already a test running
|
||||
const runningRun = this.runs.find(r => r.status === 'running');
|
||||
if (runningRun) {
|
||||
testingDashboardLog.info('Found running test:', runningRun.id);
|
||||
this.running = true;
|
||||
this.activeRunId = runningRun.id;
|
||||
|
||||
// Calculate elapsed time from when the run started
|
||||
const startTime = new Date(runningRun.timestamp);
|
||||
this.elapsedTime = Math.floor((Date.now() - startTime.getTime()) / 1000);
|
||||
|
||||
// Start elapsed time counter
|
||||
this.elapsedTimer = setInterval(() => {
|
||||
this.elapsedTime++;
|
||||
}, 1000);
|
||||
|
||||
// Start polling for status
|
||||
this.pollInterval = setInterval(() => this.pollRunStatus(), 2000);
|
||||
}
|
||||
},
|
||||
|
||||
async loadStats() {
|
||||
this.loading = true;
|
||||
this.error = null;
|
||||
|
||||
try {
|
||||
const stats = await apiClient.get('/admin/tests/stats');
|
||||
this.stats = stats;
|
||||
testingDashboardLog.info('Stats loaded:', stats);
|
||||
} catch (err) {
|
||||
testingDashboardLog.error('Failed to load stats:', err);
|
||||
this.error = err.message;
|
||||
|
||||
// Redirect to login if unauthorized
|
||||
if (err.message.includes('Unauthorized')) {
|
||||
window.location.href = '/admin/login';
|
||||
}
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
async loadRuns() {
|
||||
try {
|
||||
const runs = await apiClient.get('/admin/tests/runs?limit=10');
|
||||
this.runs = runs;
|
||||
testingDashboardLog.info('Runs loaded:', runs.length);
|
||||
} catch (err) {
|
||||
testingDashboardLog.error('Failed to load runs:', err);
|
||||
// Don't set error - stats are more important
|
||||
}
|
||||
},
|
||||
|
||||
async runTests(testPath = 'tests') {
|
||||
this.running = true;
|
||||
this.error = null;
|
||||
this.successMessage = null;
|
||||
this.elapsedTime = 0;
|
||||
|
||||
testingDashboardLog.info('Starting tests:', testPath);
|
||||
|
||||
try {
|
||||
// Start the test run (returns immediately)
|
||||
const result = await apiClient.post('/admin/tests/run', {
|
||||
test_path: testPath
|
||||
});
|
||||
|
||||
testingDashboardLog.info('Test run started:', result);
|
||||
this.activeRunId = result.id;
|
||||
|
||||
// Start elapsed time counter
|
||||
this.elapsedTimer = setInterval(() => {
|
||||
this.elapsedTime++;
|
||||
}, 1000);
|
||||
|
||||
// Start polling for status
|
||||
this.pollInterval = setInterval(() => this.pollRunStatus(), 2000);
|
||||
|
||||
Utils.showToast('Test run started...', 'info');
|
||||
|
||||
} catch (err) {
|
||||
testingDashboardLog.error('Failed to start tests:', err);
|
||||
this.error = err.message;
|
||||
this.running = false;
|
||||
Utils.showToast('Failed to start tests: ' + err.message, 'error');
|
||||
|
||||
// Redirect to login if unauthorized
|
||||
if (err.message.includes('Unauthorized')) {
|
||||
window.location.href = '/admin/login';
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
async pollRunStatus() {
|
||||
if (!this.activeRunId) return;
|
||||
|
||||
try {
|
||||
const run = await apiClient.get(`/admin/tests/runs/${this.activeRunId}`);
|
||||
|
||||
if (run.status !== 'running') {
|
||||
// Test run completed
|
||||
this.stopPolling();
|
||||
|
||||
testingDashboardLog.info('Test run completed:', run);
|
||||
|
||||
// Format success message
|
||||
const status = run.status === 'passed' ? 'All tests passed!' : 'Tests completed with failures.';
|
||||
this.successMessage = `${status} ${run.passed}/${run.total_tests} passed (${run.pass_rate.toFixed(1)}%) in ${this.formatDuration(run.duration_seconds)}`;
|
||||
|
||||
// Reload stats and runs
|
||||
await this.loadStats();
|
||||
await this.loadRuns();
|
||||
|
||||
// Show toast notification
|
||||
Utils.showToast(this.successMessage, run.status === 'passed' ? 'success' : 'warning');
|
||||
|
||||
// Clear success message after 10 seconds
|
||||
setTimeout(() => {
|
||||
this.successMessage = null;
|
||||
}, 10000);
|
||||
}
|
||||
} catch (err) {
|
||||
testingDashboardLog.error('Failed to poll run status:', err);
|
||||
// Don't stop polling on error, might be transient
|
||||
}
|
||||
},
|
||||
|
||||
stopPolling() {
|
||||
if (this.pollInterval) {
|
||||
clearInterval(this.pollInterval);
|
||||
this.pollInterval = null;
|
||||
}
|
||||
if (this.elapsedTimer) {
|
||||
clearInterval(this.elapsedTimer);
|
||||
this.elapsedTimer = null;
|
||||
}
|
||||
this.running = false;
|
||||
this.activeRunId = null;
|
||||
},
|
||||
|
||||
async collectTests() {
|
||||
this.collecting = true;
|
||||
this.error = null;
|
||||
|
||||
testingDashboardLog.info('Collecting tests');
|
||||
|
||||
try {
|
||||
const result = await apiClient.post('/admin/tests/collect');
|
||||
testingDashboardLog.info('Collection completed:', result);
|
||||
|
||||
Utils.showToast(`Collected ${result.total_tests} tests from ${result.total_files} files`, 'success');
|
||||
|
||||
// Reload stats
|
||||
await this.loadStats();
|
||||
} catch (err) {
|
||||
testingDashboardLog.error('Failed to collect tests:', err);
|
||||
Utils.showToast('Failed to collect tests: ' + err.message, 'error');
|
||||
} finally {
|
||||
this.collecting = false;
|
||||
}
|
||||
},
|
||||
|
||||
async refresh() {
|
||||
await this.loadStats();
|
||||
await this.loadRuns();
|
||||
},
|
||||
|
||||
formatDuration(seconds) {
|
||||
if (seconds === null || seconds === undefined) return 'N/A';
|
||||
if (seconds < 1) return `${Math.round(seconds * 1000)}ms`;
|
||||
if (seconds < 60) return `${seconds.toFixed(1)}s`;
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
const secs = Math.round(seconds % 60);
|
||||
return `${minutes}m ${secs}s`;
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user