metripy 0.3.2__py3-none-any.whl → 0.3.7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of metripy might be problematic. Click here for more details.

@@ -0,0 +1,546 @@
1
+ /**
2
+ * Dashboard JavaScript
3
+ * Handles main dashboard functionality, navigation, and data management
4
+ */
5
+
6
+ class Dashboard {
7
+ constructor() {
8
+ this.data = {};
9
+ this.currentSection = 'overview';
10
+ this.init();
11
+ }
12
+
13
+ init() {
14
+ this.setupEventListeners();
15
+ this.loadData();
16
+ this.updateMetrics();
17
+ //this.setupNavigation();
18
+ this.setupResponsiveMenu();
19
+ }
20
+
21
+ setupEventListeners() {
22
+ // Navigation links
23
+ /*document.querySelectorAll('.nav-link').forEach(link => {
24
+ link.addEventListener('click', (e) => {
25
+ e.preventDefault();
26
+ this.handleNavigation(e.target.closest('.nav-link'));
27
+ });
28
+ });*/
29
+
30
+ // Header action buttons removed
31
+
32
+ // Chart period controls removed - showing all data
33
+
34
+ // Window resize handler
35
+ window.addEventListener('resize', () => {
36
+ this.handleResize();
37
+ });
38
+ }
39
+
40
+ setupNavigation() {
41
+ const navItems = document.querySelectorAll('.nav-item');
42
+
43
+ navItems.forEach(item => {
44
+ const link = item.querySelector('.nav-link');
45
+ link.addEventListener('click', (e) => {
46
+ e.preventDefault();
47
+
48
+ // Remove active class from all items
49
+ navItems.forEach(navItem => navItem.classList.remove('active'));
50
+
51
+ // Add active class to clicked item
52
+ item.classList.add('active');
53
+
54
+ // Update current section
55
+ const href = link.getAttribute('href');
56
+ this.currentSection = href.replace('#', '');
57
+
58
+ // Smooth scroll or section switching logic can go here
59
+ this.switchSection(this.currentSection);
60
+ });
61
+ });
62
+ }
63
+
64
+ setupResponsiveMenu() {
65
+ // Mobile menu toggle logic
66
+ const createMenuToggle = () => {
67
+ if (window.innerWidth <= 1024 && !document.querySelector('.menu-toggle')) {
68
+ const toggle = document.createElement('button');
69
+ toggle.className = 'menu-toggle';
70
+ toggle.innerHTML = '<i class="fas fa-bars"></i>';
71
+ toggle.addEventListener('click', () => {
72
+ document.querySelector('.sidebar').classList.toggle('open');
73
+ });
74
+
75
+ document.querySelector('.page-header').prepend(toggle);
76
+ }
77
+ };
78
+
79
+ createMenuToggle();
80
+ window.addEventListener('resize', createMenuToggle);
81
+ }
82
+
83
+ handleNavigation(link) {
84
+ const href = link.getAttribute('href');
85
+ const section = href.replace('#', '');
86
+
87
+ // Update active navigation
88
+ document.querySelectorAll('.nav-item').forEach(item => {
89
+ item.classList.remove('active');
90
+ });
91
+ link.closest('.nav-item').classList.add('active');
92
+
93
+ // Switch to section
94
+ this.switchSection(section);
95
+ }
96
+
97
+ switchSection(section) {
98
+ console.log(`Switching to section: ${section}`);
99
+
100
+ // Here you can implement section switching logic
101
+ // For now, we'll just update the page title
102
+ const titles = {
103
+ overview: 'Code Metrics Overview',
104
+ files: 'File Analysis',
105
+ complexity: 'Complexity Analysis',
106
+ maintainability: 'Maintainability Report',
107
+ 'git-analysis': 'Git Analysis',
108
+ trends: 'Trend Analysis'
109
+ };
110
+
111
+ const titleElement = document.querySelector('.page-header h1');
112
+ if (titleElement && titles[section]) {
113
+ titleElement.textContent = titles[section];
114
+ }
115
+
116
+ this.currentSection = section;
117
+ this.loadSectionData(section);
118
+ }
119
+
120
+ loadSectionData(section) {
121
+ // Load section-specific data
122
+ switch (section) {
123
+ case 'overview':
124
+ this.updateMetrics();
125
+ break;
126
+ case 'files':
127
+ this.loadFilesList();
128
+ break;
129
+ case 'complexity':
130
+ this.loadComplexityData();
131
+ break;
132
+ case 'maintainability':
133
+ this.loadMaintainabilityData();
134
+ break;
135
+ case 'git-analysis':
136
+ this.loadGitAnalysis();
137
+ break;
138
+ case 'trends':
139
+ this.loadTrendsData();
140
+ break;
141
+ }
142
+ }
143
+
144
+ async loadData() {
145
+ try {
146
+ // Load git statistics from embedded data
147
+ await this.loadGitStatistics();
148
+
149
+ // Load metrics data from embedded template data
150
+ if (window.METRICS_DATA) {
151
+ this.data = {
152
+ ...this.data,
153
+ ...window.METRICS_DATA
154
+ };
155
+ console.log('Loaded metrics data from template');
156
+
157
+ // Update the UI with the loaded data
158
+ this.updateMetrics();
159
+ } else {
160
+ console.warn('METRICS_DATA not found in template, using fallback');
161
+ await this.simulateDataLoading(); // Fallback for development
162
+ this.updateMetrics();
163
+ }
164
+
165
+ } catch (error) {
166
+ console.error('Error loading data:', error);
167
+ this.showError('Failed to load dashboard data');
168
+ }
169
+ }
170
+
171
+ async loadGitStatistics() {
172
+ try {
173
+ // Check if git stats data is embedded in the page
174
+ if (window.GIT_STATS_DATA) {
175
+ console.log('Loading git statistics from embedded data');
176
+ this.data.gitCommits = this.formatGitCommitsData(window.GIT_STATS_DATA);
177
+ } else {
178
+ throw new Error('No git statistics data found. Please ensure git_stats.json data is embedded in the HTML template.');
179
+ }
180
+
181
+ } catch (error) {
182
+ console.error('Failed to load git statistics:', error);
183
+ this.showError('Failed to load git statistics: ' + error.message);
184
+ // Set empty data instead of fallback
185
+ this.data.gitCommits = [];
186
+ }
187
+ }
188
+
189
+ formatGitCommitsData(gitData) {
190
+ // Convert git statistics data to chart format
191
+ // Expected format: { "2024-01": 42, "2024-02": 38, ... }
192
+ if (gitData && typeof gitData === 'object' && !Array.isArray(gitData)) {
193
+ return Object.entries(gitData).map(([date, count]) => ({
194
+ month: this.formatMonthName(date),
195
+ commits: count,
196
+ date: new Date(date + '-01').toISOString()
197
+ })).sort((a, b) => new Date(a.date) - new Date(b.date));
198
+ } else {
199
+ // No valid data format
200
+ console.error('Git data format not recognized');
201
+ throw new Error('Invalid git statistics data format. Expected: {"2024-01": 42, "2024-02": 38, ...}');
202
+ }
203
+ }
204
+
205
+ formatMonthName(monthString) {
206
+ try {
207
+ // Handle different date formats
208
+ let date;
209
+ if (monthString.includes('-')) {
210
+ // Format: "2024-10" or "2024-10-01"
211
+ date = new Date(monthString + (monthString.split('-').length === 2 ? '-01' : ''));
212
+ } else {
213
+ date = new Date(monthString);
214
+ }
215
+
216
+ return date.toLocaleDateString('en-US', {
217
+ month: 'short',
218
+ year: 'numeric'
219
+ });
220
+ } catch (error) {
221
+ return monthString; // Return original if parsing fails
222
+ }
223
+ }
224
+
225
+ simulateDataLoading() {
226
+ return new Promise(resolve => {
227
+ setTimeout(() => {
228
+ // Preserve gitCommits data if already loaded from JSON
229
+ const existingGitCommits = this.data.gitCommits;
230
+
231
+ // Load data from external metrics file if available
232
+ if (window.METRICS_DATA) {
233
+ this.data = {
234
+ ...this.data, // Preserve existing data
235
+ ...window.METRICS_DATA, // Load metrics from external file
236
+ gitCommits: existingGitCommits || [] // Preserve git data
237
+ };
238
+ } else {
239
+ // Fallback to sample data if external data not available
240
+ console.warn('METRICS_DATA not found, using fallback sample data');
241
+ this.data = {
242
+ ...this.data,
243
+ totalLoc: 15420,
244
+ avgComplexity: 2.8,
245
+ maintainabilityIndex: 78.5,
246
+ avgMethodSize: 12.3,
247
+ gitCommits: existingGitCommits || [],
248
+ segmentation: {
249
+ loc: { good: 45, ok: 32, warning: 18, critical: 5 },
250
+ complexity: { good: 52, ok: 28, warning: 15, critical: 5 },
251
+ maintainability: { good: 35, ok: 42, warning: 18, critical: 5 },
252
+ methodSize: { good: 48, ok: 35, warning: 12, critical: 5 }
253
+ }
254
+ };
255
+ }
256
+ resolve();
257
+ }, 500);
258
+ });
259
+ }
260
+
261
+
262
+ updateMetrics() {
263
+ if (!this.data.totalLoc) return;
264
+
265
+ // Update metric values with animation
266
+ this.animateValue('total-loc', this.data.totalLoc);
267
+ this.animateValue('avg-complexity', this.data.avgComplexity, 1);
268
+ this.animateValue('maintainability-index', this.data.maintainabilityIndex, 1);
269
+ this.animateValue('avg-method-size', this.data.avgMethodSize, 1);
270
+
271
+ // Complex files and recent analysis sections removed
272
+
273
+ // Update segmentations
274
+ this.updateSegmentations();
275
+ }
276
+
277
+ animateValue(elementId, targetValue, decimals = 0) {
278
+ const element = document.getElementById(elementId);
279
+ if (!element) return;
280
+
281
+ const startValue = 0;
282
+ const duration = 2000;
283
+ const startTime = performance.now();
284
+
285
+ const animate = (currentTime) => {
286
+ const elapsed = currentTime - startTime;
287
+ const progress = Math.min(elapsed / duration, 1);
288
+
289
+ // Easing function
290
+ const easeOutQuart = 1 - Math.pow(1 - progress, 4);
291
+
292
+ const currentValue = startValue + (targetValue - startValue) * easeOutQuart;
293
+ element.textContent = currentValue.toFixed(decimals);
294
+
295
+ if (progress < 1) {
296
+ requestAnimationFrame(animate);
297
+ }
298
+ };
299
+
300
+ requestAnimationFrame(animate);
301
+ }
302
+
303
+
304
+ updateSegmentations() {
305
+ if (!this.data.segmentation) {
306
+ console.warn('No segmentation data found');
307
+ return;
308
+ }
309
+
310
+ // Update LOC segmentation
311
+ this.updateSegmentation('loc', this.data.segmentation.loc);
312
+
313
+ // Update Complexity segmentation
314
+ this.updateSegmentation('complexity', this.data.segmentation.complexity);
315
+
316
+ // Update Maintainability segmentation
317
+ this.updateSegmentation('maintainability', this.data.segmentation.maintainability);
318
+
319
+ // Update Method Size segmentation
320
+ this.updateSegmentation('methodSize', this.data.segmentation.methodSize);
321
+ }
322
+
323
+ updateSegmentation(metricType, segmentData) {
324
+ // Calculate total files to determine percentages
325
+ const total = Object.values(segmentData).reduce((sum, count) => sum + count, 0);
326
+
327
+ if (total === 0) return; // No data to display
328
+
329
+
330
+ // Get display mapping for this metric type
331
+ const displayMapping = this.getDisplayMapping(metricType);
332
+ const segmentOrder = this.getSegmentOrder(metricType);
333
+
334
+ // Calculate all percentages first to ensure they add up to 100%
335
+ let percentages = [];
336
+ let totalPercentage = 0;
337
+
338
+ segmentOrder.forEach(dataCategory => {
339
+ const count = segmentData[dataCategory] || 0;
340
+ const percentage = (count / total) * 100;
341
+ percentages.push(percentage);
342
+ totalPercentage += percentage;
343
+ });
344
+
345
+ // Adjust for any rounding errors to ensure total is exactly 100%
346
+ if (totalPercentage > 0 && totalPercentage !== 100) {
347
+ const adjustment = 100 / totalPercentage;
348
+ percentages = percentages.map(p => p * adjustment);
349
+ }
350
+
351
+ segmentOrder.forEach((dataCategory, index) => {
352
+ const count = segmentData[dataCategory] || 0;
353
+ const percentage = percentages[index];
354
+
355
+ // Get the display name for DOM queries
356
+ const displayName = displayMapping[dataCategory] || dataCategory;
357
+
358
+
359
+ // Update segment bar width
360
+ const segmentElement = document.querySelector(`[data-segment="${displayName}"]`);
361
+
362
+ if (segmentElement) {
363
+ if (count === 0) {
364
+ // Hide segments with no data
365
+ segmentElement.style.width = '0%';
366
+ segmentElement.style.minWidth = '0px';
367
+ segmentElement.removeAttribute('title');
368
+ } else {
369
+ segmentElement.style.width = `${percentage.toFixed(2)}%`;
370
+ // Only apply min-width for very small but non-zero segments
371
+ segmentElement.style.minWidth = percentage < 0.5 ? '1px' : '0px';
372
+ }
373
+ }
374
+
375
+ // Update count display and add tooltip to the label
376
+ const countElement = document.querySelector(`[data-count="${displayName}"]`);
377
+ if (countElement) {
378
+ countElement.textContent = count;
379
+
380
+ // Add tooltip to the parent segment-label for better hover area
381
+ const segmentLabel = countElement.closest('.segment-label');
382
+ if (segmentLabel) {
383
+ const tooltipText = this.getTooltipText(metricType, dataCategory, count);
384
+ if (tooltipText) {
385
+ segmentLabel.setAttribute('title', tooltipText);
386
+ }
387
+ }
388
+ }
389
+ });
390
+ }
391
+
392
+ getSegmentOrder(metricType) {
393
+ // Use consistent naming across all metrics: good, ok, warning, critical
394
+ return ['good', 'ok', 'warning', 'critical'];
395
+ }
396
+
397
+ // Map data categories to display names for each metric type
398
+ getDisplayMapping(metricType) {
399
+ const displayMap = {
400
+ loc: {
401
+ 'good': 'small',
402
+ 'ok': 'medium',
403
+ 'warning': 'large',
404
+ 'critical': 'very-large'
405
+ },
406
+ complexity: {
407
+ 'good': 'simple',
408
+ 'ok': 'moderate',
409
+ 'warning': 'complex',
410
+ 'critical': 'very-complex'
411
+ },
412
+ maintainability: {
413
+ 'good': 'excellent',
414
+ 'ok': 'good',
415
+ 'warning': 'fair',
416
+ 'critical': 'poor'
417
+ },
418
+ methodSize: {
419
+ 'good': 'concise',
420
+ 'ok': 'optimal',
421
+ 'warning': 'large',
422
+ 'critical': 'too-large'
423
+ }
424
+ };
425
+ return displayMap[metricType] || {};
426
+ }
427
+
428
+ getTooltipText(metricType, dataCategory, count) {
429
+ const tooltipMap = {
430
+ loc: {
431
+ 'good': `${count} files with 1-200 lines of code (easy to understand and maintain)`,
432
+ 'ok': `${count} files with 201-500 lines of code (reasonable size, manageable)`,
433
+ 'warning': `${count} files with 501-1000 lines of code (consider splitting into modules)`,
434
+ 'critical': `${count} files with 1000+ lines of code (urgent refactoring needed)`
435
+ },
436
+ complexity: {
437
+ 'good': `${count} files with 1-5 average cyclomatic complexity (easy to test and maintain)`,
438
+ 'ok': `${count} files with 6-10 average cyclomatic complexity (acceptable complexity)`,
439
+ 'warning': `${count} files with 11-20 average cyclomatic complexity (should consider refactoring)`,
440
+ 'critical': `${count} files with 21+ average cyclomatic complexity (high risk, urgent attention needed)`
441
+ },
442
+ maintainability: {
443
+ 'good': `${count} files with 80-100 maintainability score (highly maintainable code)`,
444
+ 'ok': `${count} files with 60-79 maintainability score (well-maintained, minor improvements)`,
445
+ 'warning': `${count} files with 40-59 maintainability score (needs attention and cleanup)`,
446
+ 'critical': `${count} files with 0-39 maintainability score (critical refactoring required)`
447
+ },
448
+ methodSize: {
449
+ 'good': `${count} files with 1-15 average lines per method (well-focused methods)`,
450
+ 'ok': `${count} files with 16-30 average lines per method (good balance of functionality)`,
451
+ 'warning': `${count} files with 31-50 average lines per method (consider breaking down)`,
452
+ 'critical': `${count} files with 51+ average lines per method (should be split immediately)`
453
+ }
454
+ };
455
+
456
+ return tooltipMap[metricType]?.[dataCategory] || '';
457
+ }
458
+
459
+
460
+ loadComplexityData() {
461
+ console.log('Loading complexity data...');
462
+ // Implement complexity data loading
463
+ }
464
+
465
+ loadMaintainabilityData() {
466
+ console.log('Loading maintainability data...');
467
+ // Implement maintainability data loading
468
+ }
469
+
470
+ loadGitAnalysis() {
471
+ console.log('Loading git analysis...');
472
+ // Implement git analysis loading
473
+ }
474
+
475
+ loadTrendsData() {
476
+ console.log('Loading trends data...');
477
+ // Implement trends data loading
478
+ }
479
+
480
+ handleResize() {
481
+ // Handle responsive behavior
482
+ if (window.innerWidth > 1024) {
483
+ document.querySelector('.sidebar')?.classList.remove('open');
484
+ }
485
+ }
486
+
487
+ showSuccess(message) {
488
+ this.showNotification(message, 'success');
489
+ }
490
+
491
+ showError(message) {
492
+ this.showNotification(message, 'error');
493
+ }
494
+
495
+ showNotification(message, type = 'info') {
496
+ // Create notification element
497
+ const notification = document.createElement('div');
498
+ notification.className = `notification notification-${type}`;
499
+ notification.style.cssText = `
500
+ position: fixed;
501
+ top: 20px;
502
+ right: 20px;
503
+ padding: 1rem 1.5rem;
504
+ border-radius: 0.5rem;
505
+ color: white;
506
+ font-weight: 500;
507
+ z-index: 1000;
508
+ transform: translateX(100%);
509
+ transition: transform 0.3s ease;
510
+ `;
511
+
512
+ // Set background color based on type
513
+ const colors = {
514
+ success: '#10b981',
515
+ error: '#ef4444',
516
+ warning: '#f59e0b',
517
+ info: '#3b82f6'
518
+ };
519
+ notification.style.backgroundColor = colors[type] || colors.info;
520
+ notification.textContent = message;
521
+
522
+ // Add to DOM
523
+ document.body.appendChild(notification);
524
+
525
+ // Animate in
526
+ setTimeout(() => {
527
+ notification.style.transform = 'translateX(0)';
528
+ }, 100);
529
+
530
+ // Remove after delay
531
+ setTimeout(() => {
532
+ notification.style.transform = 'translateX(100%)';
533
+ setTimeout(() => {
534
+ document.body.removeChild(notification);
535
+ }, 300);
536
+ }, 3000);
537
+ }
538
+ }
539
+
540
+ // Initialize dashboard when DOM is loaded
541
+ document.addEventListener('DOMContentLoaded', () => {
542
+ window.dashboard = new Dashboard();
543
+ });
544
+
545
+ // Export for global access
546
+ window.Dashboard = Dashboard;