From 7ba9b998d395e71df1bee1e08f42e6765af54497 Mon Sep 17 00:00:00 2001 From: Will Miao Date: Tue, 3 Mar 2026 15:08:33 +0800 Subject: [PATCH] fix(stats): resolve dashboard initialization race condition and test failure - Refactor StatisticsManager to return promises from initializeVisualizations and initializeLists - Update fetchAndRenderList to use the fetchData wrapper for consistent mocking - Update statistics dashboard test to include mock data for paginated model-usage-list endpoint --- static/js/statistics.js | 19 ++++++------- .../pages/statistics.dashboard.test.js | 27 +++++++++++++++++++ 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/static/js/statistics.js b/static/js/statistics.js index ea76af59..7c659a96 100644 --- a/static/js/statistics.js +++ b/static/js/statistics.js @@ -29,7 +29,7 @@ export class StatisticsManager { await this.loadAllData(); // Initialize charts and visualizations - this.initializeVisualizations(); + await this.initializeVisualizations(); this.initialized = true; } @@ -102,7 +102,7 @@ export class StatisticsManager { return response.json(); } - initializeVisualizations() { + async initializeVisualizations() { // Initialize metrics cards this.renderMetricsCards(); @@ -110,7 +110,7 @@ export class StatisticsManager { this.initializeCharts(); // Initialize lists and other components - this.initializeLists(); + await this.initializeLists(); this.renderLargestModelsList(); this.renderTagCloud(); this.renderInsights(); @@ -554,14 +554,14 @@ export class StatisticsManager { }); } - initializeLists() { + async initializeLists() { const listTypes = [ { type: 'lora', containerId: 'topLorasList' }, { type: 'checkpoint', containerId: 'topCheckpointsList' }, { type: 'embedding', containerId: 'topEmbeddingsList' } ]; - listTypes.forEach(({ type, containerId }) => { + const promises = listTypes.map(({ type, containerId }) => { const container = document.getElementById(containerId); if (container) { @@ -573,9 +573,12 @@ export class StatisticsManager { }); // Initial fetch - this.fetchAndRenderList(type, container); + return this.fetchAndRenderList(type, container); } + return Promise.resolve(); }); + + await Promise.all(promises); } async fetchAndRenderList(type, container) { @@ -591,10 +594,8 @@ export class StatisticsManager { try { const url = `/api/lm/stats/model-usage-list?type=${type}&sort=${state.sort}&offset=${state.offset}&limit=${state.limit}`; - const response = await fetch(url); - if (!response.ok) throw new Error('Network response was not ok'); + const result = await this.fetchData(url); - const result = await response.json(); if (result.success) { const items = result.data.items; diff --git a/tests/frontend/pages/statistics.dashboard.test.js b/tests/frontend/pages/statistics.dashboard.test.js index a6a94a50..ad3bd38a 100644 --- a/tests/frontend/pages/statistics.dashboard.test.js +++ b/tests/frontend/pages/statistics.dashboard.test.js @@ -107,6 +107,33 @@ describe('Statistics dashboard rendering', () => { ], }, }, + '/api/lm/stats/model-usage-list?type=lora&sort=desc&offset=0&limit=50': { + success: true, + data: { + items: [ + { name: 'Lora A', base_model: 'SDXL', folder: 'loras', usage_count: 10, preview_url: '' }, + ], + total: 1, + }, + }, + '/api/lm/stats/model-usage-list?type=checkpoint&sort=desc&offset=0&limit=50': { + success: true, + data: { + items: [ + { name: 'Checkpoint A', base_model: 'SDXL', folder: 'checkpoints', usage_count: 5, preview_url: '' }, + ], + total: 1, + }, + }, + '/api/lm/stats/model-usage-list?type=embedding&sort=desc&offset=0&limit=50': { + success: true, + data: { + items: [ + { name: 'Embedding A', base_model: 'SDXL', folder: 'embeddings', usage_count: 7, preview_url: '' }, + ], + total: 1, + }, + }, }; const { StatisticsManager } = await import(STATISTICS_MODULE);