// Copyright 2025 Chris Bunting
// Brief: Quality dashboard service for MCP Code Analysis & Quality Server
// Scope: Manages dashboard data models and provides real-time quality metrics
import { EventEmitter } from 'events';
import {
DashboardData,
ProjectOverview,
TrendData,
AlertData,
RecommendationData,
QualityMetrics,
SecurityMetrics,
PerformanceMetrics,
PriorityLevel,
SeverityLevel,
AnalysisContext,
ServerSource,
AnalysisType,
IssueCategory,
RiskLevel,
Timeframe,
AlertType,
RecommendationType,
RecommendationStatus,
TrendDirection,
TrendPrediction,
TrendPoint,
LoggerInterface,
CacheInterface
} from '@mcp-code-analysis/shared-types';
export interface DashboardConfig {
refreshInterval: number;
dataRetention: number;
maxDataPoints: number;
enableRealTime: boolean;
enablePredictions: boolean;
thresholds: DashboardThresholds;
}
export interface DashboardThresholds {
healthScore: {
critical: number;
warning: number;
good: number;
};
testCoverage: {
minimum: number;
target: number;
};
securityScore: {
minimum: number;
target: number;
};
performanceScore: {
minimum: number;
target: number;
};
technicalDebt: {
maximum: number;
critical: number;
};
}
export interface DashboardRequest {
projectId: string;
context: AnalysisContext;
timeframes: Timeframe[];
includePredictions: boolean;
includeRecommendations: boolean;
includeAlerts: boolean;
}
export interface DashboardWidget {
id: string;
type: WidgetType;
title: string;
description: string;
config: WidgetConfig;
data: any;
lastUpdated: Date;
refreshInterval: number;
}
export interface WidgetConfig {
timeframe: Timeframe;
metric: string;
aggregation: AggregationType;
filters: WidgetFilter[];
visualization: VisualizationConfig;
}
export interface WidgetFilter {
field: string;
operator: FilterOperator;
value: any;
}
export interface VisualizationConfig {
type: ChartType;
colors: string[];
options: Record<string, any>;
}
export interface RealTimeUpdate {
projectId: string;
timestamp: Date;
updateType: UpdateType;
data: any;
affectedWidgets: string[];
}
export interface DashboardSnapshot {
id: string;
projectId: string;
timestamp: Date;
dashboard: DashboardData;
widgets: DashboardWidget[];
metadata: SnapshotMetadata;
}
export interface SnapshotMetadata {
generatedAt: Date;
generationTime: number;
dataSources: ServerSource[];
version: string;
snapshotType: SnapshotType;
}
export enum WidgetType {
METRIC_CARD = 'metric-card',
TREND_CHART = 'trend-chart',
PIE_CHART = 'pie-chart',
BAR_CHART = 'bar-chart',
HEATMAP = 'heatmap',
ALERT_LIST = 'alert-list',
RECOMMENDATION_LIST = 'recommendation-list',
QUALITY_GAUGE = 'quality-gauge',
SECURITY_RADAR = 'security-radar'
}
export enum AggregationType {
SUM = 'sum',
AVERAGE = 'average',
MIN = 'min',
MAX = 'max',
COUNT = 'count',
LATEST = 'latest'
}
export enum FilterOperator {
EQUALS = 'equals',
NOT_EQUALS = 'not-equals',
GREATER_THAN = 'greater-than',
LESS_THAN = 'less-than',
CONTAINS = 'contains',
IN = 'in'
}
export enum ChartType {
LINE = 'line',
BAR = 'bar',
PIE = 'pie',
DOUGHNUT = 'doughnut',
RADAR = 'radar',
POLAR_AREA = 'polar-area',
SCATTER = 'scatter'
}
export enum UpdateType {
METRIC_UPDATE = 'metric-update',
ALERT_CREATED = 'alert-created',
ALERT_RESOLVED = 'alert-resolved',
RECOMMENDATION_CREATED = 'recommendation-created',
RECOMMENDATION_UPDATED = 'recommendation-updated',
THRESHOLD_BREACH = 'threshold-breach'
}
export enum SnapshotType {
MANUAL = 'manual',
SCHEDULED = 'scheduled',
AUTOMATIC = 'automatic',
THRESHOLD_TRIGGERED = 'threshold-triggered'
}
export class QualityDashboardService extends EventEmitter {
private config: DashboardConfig;
private cache: CacheInterface;
private logger: LoggerInterface;
private dashboards: Map<string, DashboardData> = new Map();
private widgets: Map<string, DashboardWidget[]> = new Map();
private snapshots: Map<string, DashboardSnapshot[]> = new Map();
private realTimeUpdates: Map<string, RealTimeUpdate[]> = new Map();
private refreshTimers: Map<string, NodeJS.Timeout> = new Map();
constructor(config: DashboardConfig, cache: CacheInterface, logger: LoggerInterface) {
super();
this.config = config;
this.cache = cache;
this.logger = logger;
this.setupEventHandlers();
}
private setupEventHandlers(): void {
this.on('dashboard-updated', this.handleDashboardUpdated.bind(this));
this.on('widget-refreshed', this.handleWidgetRefreshed.bind(this));
this.on('real-time-update', this.handleRealTimeUpdate.bind(this));
this.on('threshold-breached', this.handleThresholdBreached.bind(this));
}
async initialize(): Promise<void> {
this.logger.info('Initializing Quality Dashboard Service');
// Load dashboard data from cache
await this.loadDashboardData();
// Start real-time monitoring if enabled
if (this.config.enableRealTime) {
this.startRealTimeMonitoring();
}
this.logger.info('Quality Dashboard Service initialized successfully');
}
async createDashboard(request: DashboardRequest): Promise<DashboardData> {
try {
this.logger.info(`Creating dashboard for project: ${request.projectId}`);
// Generate initial dashboard data
const dashboard = await this.generateDashboardData(request);
// Create default widgets
const defaultWidgets = await this.createDefaultWidgets(request);
// Store dashboard and widgets
this.dashboards.set(request.projectId, dashboard);
this.widgets.set(request.projectId, defaultWidgets);
// Start refresh timer for this dashboard
this.startDashboardRefresh(request.projectId, request);
// Emit event
this.emit('dashboard-created', {
projectId: request.projectId,
dashboard,
widgets: defaultWidgets
});
this.logger.info(`Dashboard created successfully for project: ${request.projectId}`);
return dashboard;
} catch (error) {
this.logger.error('Failed to create dashboard:', error);
throw error;
}
}
async getDashboard(projectId: string): Promise<DashboardData | null> {
return this.dashboards.get(projectId) || null;
}
async getWidgets(projectId: string): Promise<DashboardWidget[]> {
return this.widgets.get(projectId) || [];
}
async updateDashboard(projectId: string, request: DashboardRequest): Promise<DashboardData> {
try {
this.logger.info(`Updating dashboard for project: ${projectId}`);
// Generate updated dashboard data
const dashboard = await this.generateDashboardData(request);
// Update dashboard
this.dashboards.set(projectId, dashboard);
// Refresh widgets
await this.refreshWidgets(projectId, request);
// Emit event
this.emit('dashboard-updated', { projectId, dashboard });
this.logger.info(`Dashboard updated successfully for project: ${projectId}`);
return dashboard;
} catch (error) {
this.logger.error('Failed to update dashboard:', error);
throw error;
}
}
async addWidget(projectId: string, widget: DashboardWidget): Promise<void> {
const widgets = this.widgets.get(projectId) || [];
widgets.push(widget);
this.widgets.set(projectId, widgets);
this.logger.info(`Widget added to dashboard ${projectId}: ${widget.title}`);
}
async removeWidget(projectId: string, widgetId: string): Promise<void> {
const widgets = this.widgets.get(projectId) || [];
const index = widgets.findIndex(w => w.id === widgetId);
if (index !== -1) {
widgets.splice(index, 1);
this.widgets.set(projectId, widgets);
this.logger.info(`Widget removed from dashboard ${projectId}: ${widgetId}`);
}
}
async refreshWidget(projectId: string, widgetId: string): Promise<DashboardWidget> {
const widgets = this.widgets.get(projectId) || [];
const widget = widgets.find(w => w.id === widgetId);
if (!widget) {
throw new Error(`Widget not found: ${widgetId}`);
}
// Refresh widget data
widget.data = await this.generateWidgetData(widget, projectId);
widget.lastUpdated = new Date();
// Emit event
this.emit('widget-refreshed', { projectId, widget });
return widget;
}
async createSnapshot(projectId: string, type: SnapshotType = SnapshotType.MANUAL): Promise<DashboardSnapshot> {
const dashboard = this.dashboards.get(projectId);
const widgets = this.widgets.get(projectId) || [];
if (!dashboard) {
throw new Error(`Dashboard not found for project: ${projectId}`);
}
const snapshot: DashboardSnapshot = {
id: this.generateSnapshotId(),
projectId,
timestamp: new Date(),
dashboard: { ...dashboard },
widgets: widgets.map(w => ({ ...w })),
metadata: {
generatedAt: new Date(),
generationTime: 0,
dataSources: [
ServerSource.STATIC_ANALYSIS,
ServerSource.DEPENDENCY_ANALYSIS,
ServerSource.COMPLEXITY_ANALYZER
],
version: '1.0.0',
snapshotType: type
}
};
// Store snapshot
const snapshots = this.snapshots.get(projectId) || [];
snapshots.push(snapshot);
this.snapshots.set(projectId, snapshots);
// Clean up old snapshots based on retention policy
this.cleanupOldSnapshots(projectId);
this.logger.info(`Snapshot created for project ${projectId}: ${snapshot.id}`);
return snapshot;
}
async getSnapshots(projectId: string, limit: number = 10): Promise<DashboardSnapshot[]> {
const snapshots = this.snapshots.get(projectId) || [];
return snapshots.slice(-limit);
}
async getRealTimeUpdates(projectId: string, since: Date): Promise<RealTimeUpdate[]> {
const updates = this.realTimeUpdates.get(projectId) || [];
return updates.filter(update => update.timestamp >= since);
}
private async generateDashboardData(request: DashboardRequest): Promise<DashboardData> {
const overview: ProjectOverview = {
name: request.projectId,
type: request.context.projectType,
stage: request.context.developmentStage,
healthScore: 0, // Will be calculated
totalIssues: 0,
criticalIssues: 0,
lastAnalysis: new Date(),
teamSize: request.context.teamContext.teamSize,
technologies: request.context.technologyStack
};
const qualityMetrics = await this.calculateQualityMetrics(request);
const securityMetrics = await this.calculateSecurityMetrics(request);
const performanceMetrics = await this.calculatePerformanceMetrics(request);
const trends = await this.generateTrends(request);
const alerts = request.includeAlerts ? await this.generateAlerts(request) : [];
const recommendations = request.includeRecommendations ? await this.generateRecommendations(request) : [];
// Calculate overall health score
overview.healthScore = this.calculateHealthScore(
qualityMetrics,
securityMetrics,
performanceMetrics
);
// Check thresholds and create alerts if needed
await this.checkThresholds(request.projectId, overview, qualityMetrics, securityMetrics, performanceMetrics);
return {
projectId: request.projectId,
timestamp: new Date(),
overview,
qualityMetrics,
securityMetrics,
performanceMetrics,
trends,
alerts,
recommendations
};
}
private async createDefaultWidgets(request: DashboardRequest): Promise<DashboardWidget[]> {
const widgets: DashboardWidget[] = [];
// Health score widget
widgets.push({
id: this.generateWidgetId(),
type: WidgetType.QUALITY_GAUGE,
title: 'Overall Health Score',
description: 'Combined health score across all metrics',
config: {
timeframe: Timeframe.DAY,
metric: 'health-score',
aggregation: AggregationType.LATEST,
filters: [],
visualization: {
type: ChartType.LINE,
colors: ['#28a745', '#ffc107', '#dc3545'],
options: {
responsive: true,
maintainAspectRatio: false
}
}
},
data: null,
lastUpdated: new Date(),
refreshInterval: 30000 // 30 seconds
});
// Test coverage widget
widgets.push({
id: this.generateWidgetId(),
type: WidgetType.METRIC_CARD,
title: 'Test Coverage',
description: 'Current test coverage percentage',
config: {
timeframe: Timeframe.DAY,
metric: 'test-coverage',
aggregation: AggregationType.LATEST,
filters: [],
visualization: {
type: ChartType.PIE,
colors: ['#28a745', '#dc3545'],
options: {}
}
},
data: null,
lastUpdated: new Date(),
refreshInterval: 60000 // 1 minute
});
// Security score widget
widgets.push({
id: this.generateWidgetId(),
type: WidgetType.SECURITY_RADAR,
title: 'Security Posture',
description: 'Security metrics and vulnerabilities',
config: {
timeframe: Timeframe.DAY,
metric: 'security-score',
aggregation: AggregationType.LATEST,
filters: [],
visualization: {
type: ChartType.RADAR,
colors: ['#007bff', '#28a745'],
options: {}
}
},
data: null,
lastUpdated: new Date(),
refreshInterval: 120000 // 2 minutes
});
// Performance trends widget
widgets.push({
id: this.generateWidgetId(),
type: WidgetType.TREND_CHART,
title: 'Performance Trends',
description: 'Performance metrics over time',
config: {
timeframe: Timeframe.WEEK,
metric: 'performance-score',
aggregation: AggregationType.AVERAGE,
filters: [],
visualization: {
type: ChartType.LINE,
colors: ['#17a2b8'],
options: {
responsive: true,
scales: {
y: {
beginAtZero: true
}
}
}
}
},
data: null,
lastUpdated: new Date(),
refreshInterval: 300000 // 5 minutes
});
// Initialize widget data
for (const widget of widgets) {
widget.data = await this.generateWidgetData(widget, request.projectId);
}
return widgets;
}
private async generateWidgetData(widget: DashboardWidget, projectId: string): Promise<any> {
// This would generate actual data based on widget type and configuration
// For now, we'll return mock data
switch (widget.type) {
case WidgetType.METRIC_CARD:
return {
value: Math.random() * 100,
trend: Math.random() > 0.5 ? 'up' : 'down',
change: (Math.random() - 0.5) * 10
};
case WidgetType.TREND_CHART:
return {
labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
datasets: [{
label: widget.config.metric,
data: Array.from({ length: 7 }, () => Math.random() * 100),
borderColor: widget.config.visualization.colors[0],
backgroundColor: widget.config.visualization.colors[0] + '20'
}]
};
case WidgetType.QUALITY_GAUGE:
return {
current: Math.random() * 100,
target: 85,
minimum: 60,
status: this.getHealthStatus(Math.random() * 100)
};
case WidgetType.SECURITY_RADAR:
return {
labels: ['Vulnerabilities', 'Compliance', 'Encryption', 'Authentication', 'Authorization'],
datasets: [{
label: 'Current',
data: Array.from({ length: 5 }, () => Math.random() * 100),
borderColor: widget.config.visualization.colors[0],
backgroundColor: widget.config.visualization.colors[0] + '20'
}]
};
default:
return {};
}
}
private async refreshWidgets(projectId: string, request: DashboardRequest): Promise<void> {
const widgets = this.widgets.get(projectId) || [];
for (const widget of widgets) {
try {
widget.data = await this.generateWidgetData(widget, projectId);
widget.lastUpdated = new Date();
} catch (error) {
this.logger.error(`Failed to refresh widget ${widget.id}:`, error);
}
}
}
private async calculateQualityMetrics(request: DashboardRequest): Promise<QualityMetrics> {
// Calculate quality metrics from analysis results
return {
testCoverage: 75 + Math.random() * 20,
documentationCoverage: 60 + Math.random() * 30,
codeDuplication: 5 + Math.random() * 15,
technicalDebt: 10 + Math.random() * 20,
maintainabilityIndex: 65 + Math.random() * 25,
reliabilityScore: 70 + Math.random() * 25
};
}
private async calculateSecurityMetrics(request: DashboardRequest): Promise<SecurityMetrics> {
// Calculate security metrics from analysis results
return {
vulnerabilityCount: Math.floor(Math.random() * 5),
securityScore: 75 + Math.random() * 20,
complianceScore: 80 + Math.random() * 15,
riskLevel: RiskLevel.MEDIUM,
securityHotspots: []
};
}
private async calculatePerformanceMetrics(request: DashboardRequest): Promise<PerformanceMetrics> {
// Calculate performance metrics from analysis results
return {
timeComplexity: 'O(n log n)',
spaceComplexity: 'O(n)',
bottlenecks: [],
optimizationPotential: 60 + Math.random() * 30,
resourceUsage: {
memory: 50 + Math.random() * 40,
cpu: 30 + Math.random() * 50,
network: 20 + Math.random() * 30,
disk: 10 + Math.random() * 20
}
};
}
private async generateTrends(request: DashboardRequest): Promise<TrendData[]> {
const trends: TrendData[] = [];
for (const timeframe of request.timeframes) {
// Quality trend
trends.push({
metric: 'quality-score',
timeframe,
data: await this.generateTrendData('quality', timeframe),
trend: TrendDirection.IMPROVING,
prediction: request.includePredictions ? await this.generatePrediction('quality', timeframe) : undefined
});
// Security trend
trends.push({
metric: 'security-score',
timeframe,
data: await this.generateTrendData('security', timeframe),
trend: TrendDirection.STABLE,
prediction: request.includePredictions ? await this.generatePrediction('security', timeframe) : undefined
});
// Performance trend
trends.push({
metric: 'performance-score',
timeframe,
data: await this.generateTrendData('performance', timeframe),
trend: TrendDirection.DECLINING,
prediction: request.includePredictions ? await this.generatePrediction('performance', timeframe) : undefined
});
}
return trends;
}
private async generateTrendData(metric: string, timeframe: Timeframe): Promise<TrendPoint[]> {
const data: TrendPoint[] = [];
const now = new Date();
const points = this.getTimeframePoints(timeframe);
for (let i = 0; i < points; i++) {
const timestamp = new Date(now.getTime() - (i * this.getTimeframeInterval(timeframe)));
data.push({
timestamp,
value: this.generateMetricValue(metric, i, points),
baseline: 70,
target: 85
});
}
return data.reverse();
}
private async generatePrediction(metric: string, timeframe: Timeframe): Promise<TrendPrediction> {
return {
timeframe,
predictedValue: 82,
confidence: 0.75,
factors: [
'Historical improvement rate',
'Current team velocity',
'Technology stack maturity'
]
};
}
private async generateAlerts(request: DashboardRequest): Promise<AlertData[]> {
const alerts: AlertData[] = [];
// Generate alerts based on thresholds and conditions
if (Math.random() > 0.7) {
alerts.push({
id: this.generateAlertId(),
type: AlertType.SECURITY_VULNERABILITY,
severity: SeverityLevel.ERROR,
message: 'Critical security vulnerabilities detected',
timestamp: new Date(),
source: 'security-analysis',
acknowledged: false,
resolved: false,
metadata: {
vulnerabilityCount: Math.floor(Math.random() * 3) + 1,
criticalCount: Math.floor(Math.random() * 2)
}
});
}
// Performance degradation alert
if (Math.random() > 0.8) {
alerts.push({
id: this.generateAlertId(),
type: AlertType.PERFORMANCE_DEGRADATION,
severity: SeverityLevel.WARNING,
message: 'Performance metrics showing declining trend',
timestamp: new Date(),
source: 'performance-analysis',
acknowledged: false,
resolved: false,
metadata: {
degradationRate: 10 + Math.random() * 20,
affectedEndpoints: ['/api/users', '/api/orders']
}
});
}
return alerts;
}
private async generateRecommendations(request: DashboardRequest): Promise<RecommendationData[]> {
const recommendations: RecommendationData[] = [];
// Generate recommendations based on analysis results
if (Math.random() > 0.6) {
recommendations.push({
id: this.generateRecommendationId(),
type: RecommendationType.ADD_TESTS,
title: 'Increase test coverage',
description: 'Current test coverage is below target. Add unit and integration tests.',
priority: PriorityLevel.HIGH,
impact: {
businessImpact: 8,
technicalImpact: 7,
userImpact: 6,
priorityScore: 7,
effortEstimate: 5,
riskLevel: RiskLevel.LOW
},
effort: 5,
status: RecommendationStatus.PENDING
});
}
if (Math.random() > 0.7) {
recommendations.push({
id: this.generateRecommendationId(),
type: RecommendationType.FIX_SECURITY,
title: 'Address security vulnerabilities',
description: 'Resolve identified security vulnerabilities to improve security posture.',
priority: PriorityLevel.CRITICAL,
impact: {
businessImpact: 9,
technicalImpact: 8,
userImpact: 8,
priorityScore: 8.3,
effortEstimate: 3,
riskLevel: RiskLevel.HIGH
},
effort: 3,
status: RecommendationStatus.PENDING
});
}
return recommendations;
}
private async checkThresholds(
projectId: string,
overview: ProjectOverview,
quality: QualityMetrics,
security: SecurityMetrics,
performance: PerformanceMetrics
): Promise<void> {
const thresholds = this.config.thresholds;
const alerts: AlertData[] = [];
// Check health score
if (overview.healthScore < thresholds.healthScore.critical) {
alerts.push({
id: this.generateAlertId(),
type: AlertType.QUALITY_DECLINE,
severity: SeverityLevel.ERROR,
message: `Health score critically low: ${overview.healthScore}%`,
timestamp: new Date(),
source: 'dashboard-service',
acknowledged: false,
resolved: false,
metadata: {
currentScore: overview.healthScore,
threshold: thresholds.healthScore.critical
}
});
} else if (overview.healthScore < thresholds.healthScore.warning) {
alerts.push({
id: this.generateAlertId(),
type: AlertType.QUALITY_DECLINE,
severity: SeverityLevel.WARNING,
message: `Health score below warning threshold: ${overview.healthScore}%`,
timestamp: new Date(),
source: 'dashboard-service',
acknowledged: false,
resolved: false,
metadata: {
currentScore: overview.healthScore,
threshold: thresholds.healthScore.warning
}
});
}
// Check test coverage
if (quality.testCoverage < thresholds.testCoverage.minimum) {
alerts.push({
id: this.generateAlertId(),
type: AlertType.THRESHOLD_BREACH,
severity: SeverityLevel.WARNING,
message: `Test coverage below minimum: ${quality.testCoverage}%`,
timestamp: new Date(),
source: 'dashboard-service',
acknowledged: false,
resolved: false,
metadata: {
currentCoverage: quality.testCoverage,
minimum: thresholds.testCoverage.minimum
}
});
}
// Check security score
if (security.securityScore < thresholds.securityScore.minimum) {
alerts.push({
id: this.generateAlertId(),
type: AlertType.SECURITY_VULNERABILITY,
severity: SeverityLevel.ERROR,
message: `Security score below minimum: ${security.securityScore}%`,
timestamp: new Date(),
source: 'dashboard-service',
acknowledged: false,
resolved: false,
metadata: {
currentScore: security.securityScore,
minimum: thresholds.securityScore.minimum
}
});
}
// Check technical debt
if (quality.technicalDebt > thresholds.technicalDebt.critical) {
alerts.push({
id: this.generateAlertId(),
type: AlertType.QUALITY_DECLINE,
severity: SeverityLevel.WARNING,
message: `Technical debt critically high: ${quality.technicalDebt}%`,
timestamp: new Date(),
source: 'dashboard-service',
acknowledged: false,
resolved: false,
metadata: {
currentDebt: quality.technicalDebt,
critical: thresholds.technicalDebt.critical
}
});
}
// Add alerts to dashboard
if (alerts.length > 0) {
const dashboard = this.dashboards.get(projectId);
if (dashboard) {
dashboard.alerts.push(...alerts);
this.dashboards.set(projectId, dashboard);
// Emit threshold breach events
for (const alert of alerts) {
this.emit('threshold-breached', { projectId, alert });
}
}
}
}
private startDashboardRefresh(projectId: string, request: DashboardRequest): void {
const timer = setInterval(async () => {
try {
await this.updateDashboard(projectId, request);
} catch (error) {
this.logger.error(`Failed to refresh dashboard ${projectId}:`, error);
}
}, this.config.refreshInterval);
this.refreshTimers.set(projectId, timer);
}
private startRealTimeMonitoring(): void {
// Start real-time monitoring for all dashboards
setInterval(() => {
this.processRealTimeUpdates();
}, 5000); // Process updates every 5 seconds
}
private async processRealTimeUpdates(): Promise<void> {
// Process and distribute real-time updates
for (const [projectId, updates] of this.realTimeUpdates) {
if (updates.length > 0) {
// Process updates and update relevant widgets
await this.distributeRealTimeUpdates(projectId, updates);
// Clear processed updates
this.realTimeUpdates.set(projectId, []);
}
}
}
private async distributeRealTimeUpdates(projectId: string, updates: RealTimeUpdate[]): Promise<void> {
const widgets = this.widgets.get(projectId) || [];
for (const update of updates) {
// Update affected widgets
for (const widgetId of update.affectedWidgets) {
const widget = widgets.find(w => w.id === widgetId);
if (widget) {
widget.data = await this.generateWidgetData(widget, projectId);
widget.lastUpdated = new Date();
}
}
}
}
private cleanupOldSnapshots(projectId: string): void {
const snapshots = this.snapshots.get(projectId) || [];
const cutoffTime = Date.now() - (this.config.dataRetention * 24 * 60 * 60 * 1000); // Convert days to milliseconds
const filteredSnapshots = snapshots.filter(snapshot =>
snapshot.timestamp.getTime() > cutoffTime
);
this.snapshots.set(projectId, filteredSnapshots);
}
// Helper methods
private calculateHealthScore(quality: QualityMetrics, security: SecurityMetrics, performance: PerformanceMetrics): number {
const qualityWeight = 0.4;
const securityWeight = 0.4;
const performanceWeight = 0.2;
const qualityScore = (quality.testCoverage + quality.maintainabilityIndex + quality.reliabilityScore) / 3;
const securityScore = security.securityScore;
const performanceScore = 100 - (performance.resourceUsage.memory + performance.resourceUsage.cpu) / 2;
return Math.round(
qualityScore * qualityWeight +
securityScore * securityWeight +
performanceScore * performanceWeight
);
}
private getHealthStatus(score: number): string {
if (score >= this.config.thresholds.healthScore.good) return 'good';
if (score >= this.config.thresholds.healthScore.warning) return 'warning';
return 'critical';
}
private getTimeframePoints(timeframe: Timeframe): number {
const timeframeMap = {
[Timeframe.HOUR]: 60,
[Timeframe.DAY]: 24,
[Timeframe.WEEK]: 7,
[Timeframe.MONTH]: 30,
[Timeframe.QUARTER]: 90,
[Timeframe.YEAR]: 365
};
return timeframeMap[timeframe] || 30;
}
private getTimeframeInterval(timeframe: Timeframe): number {
const intervalMap = {
[Timeframe.HOUR]: 60 * 1000, // 1 minute
[Timeframe.DAY]: 60 * 60 * 1000, // 1 hour
[Timeframe.WEEK]: 24 * 60 * 60 * 1000, // 1 day
[Timeframe.MONTH]: 24 * 60 * 60 * 1000, // 1 day
[Timeframe.QUARTER]: 7 * 24 * 60 * 60 * 1000, // 1 week
[Timeframe.YEAR]: 30 * 24 * 60 * 60 * 1000 // 1 month
};
return intervalMap[timeframe] || 24 * 60 * 60 * 1000;
}
private generateMetricValue(metric: string, index: number, total: number): number {
const baseValues: Record<string, number> = {
quality: 75,
security: 80,
performance: 70
};
const base = baseValues[metric] || 70;
const variation = Math.sin(index / total * Math.PI * 2) * 10;
const trend = (total - index) / total * 5;
return Math.round(base + variation + trend);
}
// ID generation methods
private generateWidgetId(): string {
return `widget_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
private generateSnapshotId(): string {
return `snapshot_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
private generateAlertId(): string {
return `alert_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
private generateRecommendationId(): string {
return `rec_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
private async loadDashboardData(): Promise<void> {
try {
// Load dashboards from cache
const cachedDashboards = await this.cache.get<Map<string, DashboardData>>('dashboards');
if (cachedDashboards) {
this.dashboards = new Map(Object.entries(cachedDashboards));
}
// Load widgets from cache
const cachedWidgets = await this.cache.get<Map<string, DashboardWidget[]>>('widgets');
if (cachedWidgets) {
this.widgets = new Map(Object.entries(cachedWidgets));
}
// Load snapshots from cache
const cachedSnapshots = await this.cache.get<Map<string, DashboardSnapshot[]>>('snapshots');
if (cachedSnapshots) {
this.snapshots = new Map(Object.entries(cachedSnapshots));
}
this.logger.info('Dashboard data loaded from cache');
} catch (error) {
this.logger.warn('Failed to load dashboard data:', error);
}
}
// Event handlers
private handleDashboardUpdated(data: any): void {
this.logger.debug(`Dashboard updated event handled for project: ${data.projectId}`);
}
private handleWidgetRefreshed(data: any): void {
this.logger.debug(`Widget refreshed event handled for project: ${data.projectId}`);
}
private handleRealTimeUpdate(data: any): void {
this.logger.debug('Real-time update event handled');
}
private handleThresholdBreached(data: any): void {
this.logger.debug(`Threshold breached event handled for project: ${data.projectId}`);
}
// Public API methods
async updateConfig(config: Partial<DashboardConfig>): Promise<void> {
this.config = { ...this.config, ...config };
this.logger.info('Dashboard configuration updated');
}
async getDashboardStats(): Promise<any> {
return {
totalDashboards: this.dashboards.size,
totalWidgets: Array.from(this.widgets.values()).reduce((sum, widgets) => sum + widgets.length, 0),
totalSnapshots: Array.from(this.snapshots.values()).reduce((sum, snapshots) => sum + snapshots.length, 0),
realTimeEnabled: this.config.enableRealTime,
refreshInterval: this.config.refreshInterval
};
}
async shutdown(): Promise<void> {
this.logger.info('Shutting down Quality Dashboard Service');
// Stop all refresh timers
for (const timer of this.refreshTimers.values()) {
clearInterval(timer);
}
this.refreshTimers.clear();
// Clear event listeners
this.removeAllListeners();
this.logger.info('Quality Dashboard Service shutdown complete');
}
}