UNPKG

sonarqube-issues-exporter

Version:

Enterprise-level SonarQube issues exporter with TypeScript support for generating comprehensive HTML reports with dark theme

206 lines 8.21 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.HtmlExporter = void 0; const promises_1 = require("fs/promises"); const fs_1 = require("fs"); const path_1 = require("path"); const handlebars_1 = __importDefault(require("handlebars")); const utils_1 = require("../utils"); const sonarqube_1 = require("../services/sonarqube"); class HtmlExporter { logger = (0, utils_1.getLogger)(); config; constructor(config) { this.config = config; this.registerHandlebarsHelpers(); } registerHandlebarsHelpers() { // Helper to add two numbers handlebars_1.default.registerHelper('add', (a, b) => { return (a || 0) + (b || 0); }); // Helper to format dates handlebars_1.default.registerHelper('formatDate', (date) => { return (0, utils_1.formatDate)(date); }); // Helper to escape HTML handlebars_1.default.registerHelper('escapeHtml', (str) => { return (0, utils_1.escapeHtml)(str); }); // Helper for equality comparison handlebars_1.default.registerHelper('eq', (a, b) => { return a === b; }); // Helper for greater than comparison handlebars_1.default.registerHelper('gt', (a, b) => { return a > b; }); // Helper for logical OR handlebars_1.default.registerHelper('or', (...args) => { // Remove the options object (last argument) const values = args.slice(0, -1); return values.some(Boolean); }); // Helper for conditional rendering handlebars_1.default.registerHelper('if_eq', function (a, b, options) { if (a === b) { return options.fn(this); } return options.inverse(this); }); // Helper to get current year handlebars_1.default.registerHelper('currentYear', () => { return new Date().getFullYear(); }); } processIssues(issues) { return issues.map((issue) => ({ key: issue.key, file: (0, utils_1.extractFilename)(issue.component), line: issue.line || 'N/A', message: (0, utils_1.escapeHtml)(issue.message), severity: issue.severity, status: issue.status, type: issue.type, rule: issue.rule, component: issue.component, creationDate: (0, utils_1.formatDate)(issue.creationDate), updateDate: issue.updateDate ? (0, utils_1.formatDate)(issue.updateDate) : undefined, assignee: issue.assignee, author: issue.author, tags: issue.tags || [], effort: issue.effort, debt: issue.debt, })); } calculateReportMetrics(issues) { const metricsCalculator = (0, utils_1.calculateMetrics)(issues, { severities: (issue) => issue.severity, types: (issue) => issue.type, statuses: (issue) => issue.status, components: (issue) => issue.file, rules: (issue) => issue.rule, }); return { total: issues.length, severities: metricsCalculator.severities || {}, types: metricsCalculator.types || {}, statuses: metricsCalculator.statuses || {}, components: metricsCalculator.components || {}, rules: metricsCalculator.rules || {}, }; } createReportMetadata(issues) { return { generatedAt: (0, utils_1.formatDate)(new Date()), projectKey: this.config.sonarqube.projectKey, sonarQubeUrl: this.config.sonarqube.url, totalIssues: issues.length, reportVersion: '2.0.0', filters: { excludedStatuses: this.config.export.excludeStatuses, includeResolvedIssues: this.config.export.includeResolvedIssues, }, }; } async loadTemplate(templateName) { const templatePath = (0, path_1.join)(__dirname, '..', 'templates', `${templateName}.hbs`); if (!(0, fs_1.existsSync)(templatePath)) { throw new Error(`Template not found: ${templatePath}`); } try { return await (0, promises_1.readFile)(templatePath, 'utf-8'); } catch (error) { throw new Error(`Failed to read template: ${error}`); } } async export(issues, options = {}) { const { outputPath = this.config.export.outputPath, filename = this.config.export.filename, template = this.config.export.template, } = options; try { this.logger.info(`Starting HTML export with ${issues.length} issues`); // Process issues const processedIssues = this.processIssues(issues); const metrics = this.calculateReportMetrics(processedIssues); const metadata = this.createReportMetadata(processedIssues); // Fetch enhanced data using SonarQube service const enhancedData = await this.fetchEnhancedData(); // Prepare template data const templateData = { issues: processedIssues, metrics, metadata, ...enhancedData, }; // Load and compile template const templateContent = await this.loadTemplate(template); const compiledTemplate = handlebars_1.default.compile(templateContent); // Generate HTML const html = compiledTemplate(templateData); // Ensure output directory exists const fullOutputPath = (0, path_1.join)(process.cwd(), outputPath); if (!(0, fs_1.existsSync)(fullOutputPath)) { await (0, promises_1.mkdir)(fullOutputPath, { recursive: true }); } // Write HTML file const filePath = (0, path_1.join)(fullOutputPath, filename); await (0, promises_1.writeFile)(filePath, html, 'utf-8'); this.logger.info(`HTML report generated successfully: ${filePath}`); return { success: true, outputPath: filePath, issuesCount: issues.length, metrics, }; } catch (error) { this.logger.error('Failed to generate HTML report:', error); return { success: false, outputPath: '', issuesCount: 0, metrics: { total: 0, severities: {}, types: {}, statuses: {}, components: {}, rules: {}, }, error: error instanceof Error ? error.message : String(error), }; } } /** * Fetch enhanced data from SonarQube API */ async fetchEnhancedData() { const sonarQubeService = new sonarqube_1.SonarQubeService(this.config.sonarqube); try { const [qualityGate, projectMeasures, securityHotspots] = await Promise.all([ sonarQubeService.getQualityGateStatus(), sonarQubeService.getProjectMeasures(), sonarQubeService.getSecurityHotspots(), ]); return { qualityGate, projectMeasures, securityHotspots, }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); this.logger.warn('Failed to fetch enhanced data, using defaults:', errorMessage); return { qualityGate: { status: 'NONE', conditions: [] }, projectMeasures: {}, securityHotspots: { total: 0, byPriority: {}, byCategory: {}, hotspots: [] }, }; } } } exports.HtmlExporter = HtmlExporter; //# sourceMappingURL=html.js.map