UNPKG

deepsource-mcp-server

Version:
338 lines 11.8 kB
/** * @fileoverview QualityMetrics aggregate root * * This module defines the QualityMetrics aggregate which represents quality metrics * for a project with their thresholds, measurements, and history. */ import { AggregateRoot } from '../../shared/aggregate-root.js'; import { MetricValue } from '../../value-objects/metric-value.js'; /** * QualityMetrics aggregate root * * Represents quality metrics for a project with configuration, thresholds, and history. * Tracks metric values over time and evaluates compliance with thresholds. * * @example * ```typescript * const metrics = QualityMetrics.create({ * projectKey: asProjectKey('my-project'), * repositoryId: asGraphQLNodeId('repo123'), * configuration: { * name: 'Line Coverage', * shortcode: MetricShortcode.LCV, * metricKey: MetricKey.AGGREGATE, * unit: '%', * minAllowed: 0, * maxAllowed: 100, * positiveDirection: 'UPWARD', * isReported: true, * isThresholdEnforced: true, * threshold: ThresholdValue.createPercentage(80) * } * }); * * metrics.recordMeasurement({ value: 85.5, commitOid: 'abc123' }); * console.log(metrics.isCompliant); // true * ``` */ export class QualityMetrics extends AggregateRoot { _projectKey; _repositoryId; _configuration; _currentValue; _history; _lastUpdated; static MAX_HISTORY_ENTRIES = 100; constructor(id, projectKey, repositoryId, configuration, currentValue, history, lastUpdated) { super(id); this._projectKey = projectKey; this._repositoryId = repositoryId; this._configuration = configuration; this._currentValue = currentValue; this._history = history; this._lastUpdated = lastUpdated; } /** * Creates a new QualityMetrics aggregate * * @param params - Creation parameters * @returns A new QualityMetrics instance */ static create(params) { const { projectKey, repositoryId, configuration, currentValue, history } = params; // Validate configuration if (!configuration.name || configuration.name.trim().length === 0) { throw new Error('Metric name cannot be empty'); } if (configuration.minAllowed >= configuration.maxAllowed) { throw new Error('Min allowed value must be less than max allowed value'); } // Create composite ID const id = QualityMetrics.createId({ projectKey, metricKey: configuration.metricKey, shortcode: configuration.shortcode, }); const now = new Date(); const metrics = new QualityMetrics(id, projectKey, repositoryId, configuration, currentValue || null, history || [], now); metrics.addDomainEvent({ aggregateId: id, eventType: 'QualityMetricsCreated', occurredAt: now, payload: { projectKey, metricKey: configuration.metricKey, shortcode: configuration.shortcode, threshold: configuration.threshold?.value, }, }); return metrics; } /** * Creates a composite ID for the aggregate */ static createId(id) { return `${id.projectKey}:${id.metricKey}:${id.shortcode}`; } /** * Gets the project key */ get projectKey() { return this._projectKey; } /** * Gets the repository ID */ get repositoryId() { return this._repositoryId; } /** * Gets the metric configuration */ get configuration() { return { ...this._configuration }; } /** * Gets the current metric value */ get currentValue() { return this._currentValue; } /** * Gets the metric history */ get history() { return [...this._history]; } /** * Gets the last update timestamp */ get lastUpdated() { return this._lastUpdated; } /** * Checks if the metric is currently compliant with its threshold */ get isCompliant() { if (!this._configuration.threshold || !this._currentValue) { return true; // No threshold or no value means compliant } return this._configuration.threshold.isMet(this._currentValue.value, this._configuration.positiveDirection === 'UPWARD' ? 'upward' : 'downward'); } /** * Gets the current threshold status */ get thresholdStatus() { if (!this._configuration.threshold || !this._currentValue) { return 'UNKNOWN'; } return this.isCompliant ? 'PASSING' : 'FAILING'; } /** * Updates the metric threshold * * @param threshold - New threshold value or null to remove */ updateThreshold(threshold) { const oldThreshold = this._configuration.threshold; if (threshold && threshold.unit !== this._configuration.unit) { throw new Error(`Threshold unit '${threshold.unit}' does not match metric unit '${this._configuration.unit}'`); } if (threshold && (threshold.value < this._configuration.minAllowed || threshold.value > this._configuration.maxAllowed)) { throw new Error('Threshold value is outside allowed range for this metric'); } this._configuration.threshold = threshold; this._lastUpdated = new Date(); this.addDomainEvent({ aggregateId: this._id, eventType: 'MetricThresholdUpdated', occurredAt: this._lastUpdated, payload: { oldThreshold: oldThreshold?.value, newThreshold: threshold?.value, unit: this._configuration.unit, }, }); this.markAsModified(); } /** * Updates the metric configuration * * @param params - Configuration update parameters */ updateConfiguration(params) { let hasChanges = false; if (params.name !== undefined) { const trimmedName = params.name.trim(); if (trimmedName.length === 0) { throw new Error('Metric name cannot be empty'); } if (trimmedName !== this._configuration.name) { this._configuration.name = trimmedName; hasChanges = true; } } if (params.description !== undefined && params.description !== this._configuration.description) { this._configuration.description = params.description; hasChanges = true; } if (params.isReported !== undefined && params.isReported !== this._configuration.isReported) { this._configuration.isReported = params.isReported; hasChanges = true; } if (params.isThresholdEnforced !== undefined && params.isThresholdEnforced !== this._configuration.isThresholdEnforced) { this._configuration.isThresholdEnforced = params.isThresholdEnforced; hasChanges = true; } if (hasChanges) { this._lastUpdated = new Date(); this.addDomainEvent({ aggregateId: this._id, eventType: 'MetricConfigurationUpdated', occurredAt: this._lastUpdated, payload: params, }); this.markAsModified(); } } /** * Records a new measurement * * @param params - Measurement parameters */ recordMeasurement(params) { const { value, commitOid, measuredAt } = params; if (value < this._configuration.minAllowed || value > this._configuration.maxAllowed) { throw new Error(`Value ${value} is outside allowed range [${this._configuration.minAllowed}, ${this._configuration.maxAllowed}]`); } const timestamp = measuredAt || new Date(); const metricValue = MetricValue.create(value, this._configuration.unit, undefined, timestamp); // Update current value this._currentValue = metricValue; // Add to history const historyEntry = { value: metricValue, threshold: this._configuration.threshold, thresholdStatus: this.thresholdStatus, commitOid, recordedAt: timestamp, }; this._history.push(historyEntry); // Trim history if needed if (this._history.length > QualityMetrics.MAX_HISTORY_ENTRIES) { this._history = this._history.slice(-QualityMetrics.MAX_HISTORY_ENTRIES); } this._lastUpdated = timestamp; this.addDomainEvent({ aggregateId: this._id, eventType: 'MeasurementRecorded', occurredAt: timestamp, payload: { value, unit: this._configuration.unit, commitOid, thresholdStatus: this.thresholdStatus, }, }); this.markAsModified(); } /** * Evaluates compliance at a specific point in time * * @param value - The value to evaluate * @returns Whether the value meets the threshold */ evaluateCompliance(value) { if (!this._configuration.threshold) { return true; } return this._configuration.threshold.isMet(value, this._configuration.positiveDirection === 'UPWARD' ? 'upward' : 'downward'); } /** * Gets the trend over a specified period * * @param periodDays - Number of days to analyze * @returns Trend information or null if insufficient data */ getTrend(periodDays = 30) { if (this._history.length < 2) { return null; } const cutoffDate = new Date(); cutoffDate.setDate(cutoffDate.getDate() - periodDays); const recentHistory = this._history.filter((entry) => entry.recordedAt >= cutoffDate); if (recentHistory.length < 2) { return null; } const firstValue = recentHistory[0]?.value.value; const lastValue = recentHistory[recentHistory.length - 1]?.value.value; if (firstValue === undefined || lastValue === undefined) { return null; } const changePercentage = ((lastValue - firstValue) / firstValue) * 100; let direction; if (Math.abs(changePercentage) < 1) { direction = 'STABLE'; } else if ((this._configuration.positiveDirection === 'UPWARD' && changePercentage > 0) || (this._configuration.positiveDirection === 'DOWNWARD' && changePercentage < 0)) { direction = 'IMPROVING'; } else { direction = 'DEGRADING'; } return { direction, changePercentage, periodDays, }; } /** * Reconstructs QualityMetrics from persisted data * * @param data - Persisted metrics data * @returns A reconstructed QualityMetrics instance */ static fromPersistence(data) { return new QualityMetrics(data.id, data.projectKey, data.repositoryId, data.configuration, data.currentValue, data.history, data.lastUpdated); } /** * Converts the metrics to a persistence-friendly format */ toPersistence() { return { id: this._id, projectKey: this._projectKey, repositoryId: this._repositoryId, configuration: { ...this._configuration }, currentValue: this._currentValue, history: [...this._history], lastUpdated: this._lastUpdated, }; } } //# sourceMappingURL=quality-metrics.aggregate.js.map