From b94f0f59da495c052b574d250cb5ad796edd235b Mon Sep 17 00:00:00 2001 From: "theo.gillet" Date: Tue, 12 May 2026 15:30:34 +0200 Subject: [PATCH 1/7] feat: Implement full AuditRun tracking for both scheduled and manual audits --- .../src/auditors/DynamicAuditorManager.ts | 4 +- .../src/auditors/SonarQubeAuditorManager.ts | 17 +- .../green-hosting/GreenHostingAuditor.ts | 13 +- .../auditors/lighthouse/LighthouseAuditor.ts | 26 ++- .../src/auditors/types/AuditCommonsType.ts | 1 + .../src/auditors/types/LighthouseAuditType.ts | 3 +- .../src/routes/DynamicAuditorRouter.ts | 3 +- .../src/workers/GreenHostingAnalysisWorker.ts | 5 +- .../src/workers/LighthouseAnalysisWorker.ts | 4 +- .../src/workers/SonarQubeAnalysisWorker.ts | 5 +- .../src/managers/ApplicationManager.ts | 63 ++++- .../src/managers/AuditManager.ts | 22 +- .../src/managers/BranchManager.ts | 29 ++- .../bfb-main-analyzer/src/managers/Types.ts | 1 + .../src/auditors/StaticAuditorManager.ts | 8 +- .../bundle-analyzer/BundleAnalyzeAuditor.ts | 16 +- .../code-complexity/CodeComplexityAuditor.ts | 16 +- .../code-coupling/CodeCouplingAuditor.ts | 16 +- .../CodeDuplicationAuditor.ts | 18 +- .../code-modularity/CodeModularityAuditor.ts | 11 + .../code-security/CodeSecurityAuditor.ts | 16 +- .../DependenciesAuditor.ts | 7 +- .../ecological-impact/CreedengoAuditor.ts | 16 +- .../src/auditors/types/AuditCommonsType.ts | 1 + .../auditors/types/CodeModularityAuditType.ts | 1 + .../src/auditors/types/CreedengoAuditType.ts | 1 + .../src/routes/StaticAuditorRouter.ts | 3 +- .../src/workers/BundleAnalyzeWorker.ts | 4 +- .../src/workers/CodeQualityAnalysisWorker.ts | 9 +- .../src/workers/DependenciesAnalysisWorker.ts | 4 +- .../application/ApplicationMutations.ts | 219 ++++++++++++++++++ .../application/ApplicationQueries.ts | 160 +++++++++++++ v6y-apps/bff/src/types/VitalityTypes.ts | 6 + .../application/ApplicationMutationsType.ts | 1 + .../application/ApplicationQueriesType.ts | 4 + .../TriggerApplicationAnalysisInput.ts | 14 ++ .../TriggerApplicationAnalysisOutput.ts | 20 ++ v6y-apps/bff/src/types/audit/AuditRunType.ts | 38 +++ .../migration.sql | 36 +++ v6y-libs/core-logic/prisma/schema.prisma | 24 ++ .../core-logic/src/database/AuditProvider.ts | 2 + .../src/database/AuditRunProvider.ts | 171 ++++++++++++++ v6y-libs/core-logic/src/index.ts | 2 + v6y-libs/core-logic/src/types/AuditRunType.ts | 12 + v6y-libs/core-logic/src/types/AuditType.ts | 1 + v6y-libs/core-logic/src/types/index.ts | 1 + 46 files changed, 1021 insertions(+), 33 deletions(-) create mode 100644 v6y-apps/bff/src/types/application/TriggerApplicationAnalysisInput.ts create mode 100644 v6y-apps/bff/src/types/application/TriggerApplicationAnalysisOutput.ts create mode 100644 v6y-apps/bff/src/types/audit/AuditRunType.ts create mode 100644 v6y-libs/core-logic/prisma/migrations/20260512000000_add_audit_run_history_tracking/migration.sql create mode 100644 v6y-libs/core-logic/src/database/AuditRunProvider.ts create mode 100644 v6y-libs/core-logic/src/types/AuditRunType.ts diff --git a/v6y-apps/bfb-dynamic-auditor/src/auditors/DynamicAuditorManager.ts b/v6y-apps/bfb-dynamic-auditor/src/auditors/DynamicAuditorManager.ts index cc642978..7e7f8b83 100644 --- a/v6y-apps/bfb-dynamic-auditor/src/auditors/DynamicAuditorManager.ts +++ b/v6y-apps/bfb-dynamic-auditor/src/auditors/DynamicAuditorManager.ts @@ -6,17 +6,19 @@ import { AuditCommonsType } from './types/AuditCommonsType.ts'; const { forkWorker } = WorkerHelper; const { currentConfig } = ServerConfig; -const startDynamicAudit = async ({ applicationId }: AuditCommonsType) => { +const startDynamicAudit = async ({ applicationId, auditRunId }: AuditCommonsType) => { try { AppLogger.info( '[DynamicAuditorManager - startDynamicAudit] applicationId: ', applicationId, ); + AppLogger.info('[DynamicAuditorManager - startDynamicAudit] auditRunId: ', auditRunId); // Start audits in a worker thread to prevent blocking the main thread const workerConfig = { ...currentConfig, applicationId, + auditRunId, }; // start Lighthouse analysis diff --git a/v6y-apps/bfb-dynamic-auditor/src/auditors/SonarQubeAuditorManager.ts b/v6y-apps/bfb-dynamic-auditor/src/auditors/SonarQubeAuditorManager.ts index 8743140b..819d30c3 100644 --- a/v6y-apps/bfb-dynamic-auditor/src/auditors/SonarQubeAuditorManager.ts +++ b/v6y-apps/bfb-dynamic-auditor/src/auditors/SonarQubeAuditorManager.ts @@ -149,11 +149,17 @@ const fetchSonarQubeData = async ( * Fetches SonarQube metrics for an application and persists them as AuditType records in the DB. * Follows the same batch auditor pattern as LighthouseAuditor. */ -const startAuditorAnalysis = async ({ applicationId }: AuditCommonsType): Promise => { +const startAuditorAnalysis = async ({ + applicationId, + auditRunId, +}: AuditCommonsType): Promise => { try { AppLogger.info( `[SonarQubeAuditorManager - startAuditorAnalysis] applicationId: ${applicationId}`, ); + AppLogger.info( + `[SonarQubeAuditorManager - startAuditorAnalysis] auditRunId: ${auditRunId}`, + ); if (applicationId === undefined) { return false; @@ -236,6 +242,15 @@ const startAuditorAnalysis = async ({ applicationId }: AuditCommonsType): Promis }); } + // Add auditRunId to each report + if (auditRunId) { + const auditRunIdNum = + typeof auditRunId === 'string' ? parseInt(auditRunId, 10) : auditRunId; + auditReports.forEach((audit) => { + audit.auditRunId = auditRunIdNum; + }); + } + await AuditProvider.insertAuditList(auditReports); AppLogger.info( diff --git a/v6y-apps/bfb-dynamic-auditor/src/auditors/green-hosting/GreenHostingAuditor.ts b/v6y-apps/bfb-dynamic-auditor/src/auditors/green-hosting/GreenHostingAuditor.ts index f77819aa..432357f7 100644 --- a/v6y-apps/bfb-dynamic-auditor/src/auditors/green-hosting/GreenHostingAuditor.ts +++ b/v6y-apps/bfb-dynamic-auditor/src/auditors/green-hosting/GreenHostingAuditor.ts @@ -10,12 +10,14 @@ const { formatGreenHostingReports } = GreenHostingUtils; * Iterates over production links and checks them against * The Green Web Foundation API. * @param applicationId + * @param auditRunId */ -const startAuditorAnalysis = async ({ applicationId }: AuditCommonsType) => { +const startAuditorAnalysis = async ({ applicationId, auditRunId }: AuditCommonsType) => { try { AppLogger.info( `[GreenHostingAuditor - startAuditorAnalysis] applicationId: ${applicationId}`, ); + AppLogger.info(`[GreenHostingAuditor - startAuditorAnalysis] auditRunId: ${auditRunId}`); if (applicationId === undefined) { AppLogger.warn( @@ -68,6 +70,15 @@ const startAuditorAnalysis = async ({ applicationId }: AuditCommonsType) => { return true; } + // Add auditRunId to each report + if (auditRunId) { + const auditRunIdNum = + typeof auditRunId === 'string' ? parseInt(auditRunId, 10) : auditRunId; + greenHostingReports.forEach((audit) => { + audit.auditRunId = auditRunIdNum; + }); + } + await AuditProvider.insertAuditList(greenHostingReports); AppLogger.info( diff --git a/v6y-apps/bfb-dynamic-auditor/src/auditors/lighthouse/LighthouseAuditor.ts b/v6y-apps/bfb-dynamic-auditor/src/auditors/lighthouse/LighthouseAuditor.ts index 2fa6c0da..5138896f 100644 --- a/v6y-apps/bfb-dynamic-auditor/src/auditors/lighthouse/LighthouseAuditor.ts +++ b/v6y-apps/bfb-dynamic-auditor/src/auditors/lighthouse/LighthouseAuditor.ts @@ -75,13 +75,19 @@ const startLighthouseAudit = async (auditConfig: LighthouseAuditConfigType) => { /** * Starts the Lighthouse audit for a given application. * @param applicationId + * @param auditRunId * @param browserPath */ -const startAuditorAnalysis = async ({ applicationId, browserPath }: LighthouseAuditConfigType) => { +const startAuditorAnalysis = async ({ + applicationId, + auditRunId, + browserPath, +}: LighthouseAuditConfigType) => { try { AppLogger.info( `[LighthouseAuditor - startAuditorAnalysis] applicationId: ${applicationId}`, ); + AppLogger.info(`[LighthouseAuditor - startAuditorAnalysis] auditRunId: ${auditRunId}`); AppLogger.info(`[LighthouseAuditor - startAuditorAnalysis] browserPath: ${browserPath}`); if (applicationId === undefined) { @@ -164,6 +170,15 @@ const startAuditorAnalysis = async ({ applicationId, browserPath }: LighthouseAu `[LighthouseAuditor - startAuditorAnalysis] auditReports: ${auditReports?.length}`, ); + // Add auditRunId to each report + if (auditRunId) { + const auditRunIdNum = + typeof auditRunId === 'string' ? parseInt(auditRunId, 10) : auditRunId; + auditReports.forEach((audit) => { + audit.auditRunId = auditRunIdNum; + }); + } + await AuditProvider.insertAuditList(auditReports); AppLogger.info( @@ -186,6 +201,15 @@ const startAuditorAnalysis = async ({ applicationId, browserPath }: LighthouseAu `[LighthouseAuditor - startAuditorAnalysis] ecoIndexReports: ${ecoIndexReports?.length}`, ); + // Add auditRunId to each ecoIndex report + if (auditRunId) { + const auditRunIdNum = + typeof auditRunId === 'string' ? parseInt(auditRunId, 10) : auditRunId; + ecoIndexReports.forEach((audit) => { + audit.auditRunId = auditRunIdNum; + }); + } + await AuditProvider.insertAuditList(ecoIndexReports); AppLogger.info( diff --git a/v6y-apps/bfb-dynamic-auditor/src/auditors/types/AuditCommonsType.ts b/v6y-apps/bfb-dynamic-auditor/src/auditors/types/AuditCommonsType.ts index 2b170bc4..93740447 100644 --- a/v6y-apps/bfb-dynamic-auditor/src/auditors/types/AuditCommonsType.ts +++ b/v6y-apps/bfb-dynamic-auditor/src/auditors/types/AuditCommonsType.ts @@ -4,4 +4,5 @@ export interface AuditCommonsType { applicationId?: number; application?: ApplicationType; workspaceFolder?: string; + auditRunId?: string; } diff --git a/v6y-apps/bfb-dynamic-auditor/src/auditors/types/LighthouseAuditType.ts b/v6y-apps/bfb-dynamic-auditor/src/auditors/types/LighthouseAuditType.ts index 4bf9d70b..87db4350 100644 --- a/v6y-apps/bfb-dynamic-auditor/src/auditors/types/LighthouseAuditType.ts +++ b/v6y-apps/bfb-dynamic-auditor/src/auditors/types/LighthouseAuditType.ts @@ -25,9 +25,8 @@ export type LighthouseDeviceConfigType = { export interface LighthouseAuditConfigType { applicationId?: number; - link?: string; + auditRunId?: string; browserPath?: string; - lightHouseConfig?: LighthouseDeviceConfigType; } export interface LighthouseAuditCategoryType { diff --git a/v6y-apps/bfb-dynamic-auditor/src/routes/DynamicAuditorRouter.ts b/v6y-apps/bfb-dynamic-auditor/src/routes/DynamicAuditorRouter.ts index 29ff8519..430950ac 100644 --- a/v6y-apps/bfb-dynamic-auditor/src/routes/DynamicAuditorRouter.ts +++ b/v6y-apps/bfb-dynamic-auditor/src/routes/DynamicAuditorRouter.ts @@ -12,11 +12,12 @@ DynamicAuditorRouter.post('/start-dynamic-auditor.json', async (request, respons try { AppLogger.debug('[DynamicAuditorApiPath] Entering service: [start-dynamic-auditor]'); - const { applicationId } = request.body || {}; + const { applicationId, auditRunId } = request.body || {}; // ********************************************** Start Audits *********************************************** const auditsStartedSuccessfully = await DynamicAuditorManager.startDynamicAudit({ applicationId, + auditRunId, }); // Wait for audits to potentially complete // ********************************************** Server Response *********************************************** diff --git a/v6y-apps/bfb-dynamic-auditor/src/workers/GreenHostingAnalysisWorker.ts b/v6y-apps/bfb-dynamic-auditor/src/workers/GreenHostingAnalysisWorker.ts index a9d70039..ce61fd91 100644 --- a/v6y-apps/bfb-dynamic-auditor/src/workers/GreenHostingAnalysisWorker.ts +++ b/v6y-apps/bfb-dynamic-auditor/src/workers/GreenHostingAnalysisWorker.ts @@ -7,13 +7,14 @@ import GreenHostingAuditor from '../auditors/green-hosting/GreenHostingAuditor.t AppLogger.info('******************** Starting Green Hosting Audit **************************'); try { - const { applicationId } = workerData || {}; + const { applicationId, auditRunId } = workerData || {}; AppLogger.info(`[GreenHostingAnalysisWorker] applicationId: ${applicationId}`); + AppLogger.info(`[GreenHostingAnalysisWorker] auditRunId: ${auditRunId}`); await DataBaseManager.connect(); PerformancesUtils.startMeasure('GreenHostingAnalysisWorker-startAuditorAnalysis'); - await GreenHostingAuditor.startAuditorAnalysis({ applicationId }); + await GreenHostingAuditor.startAuditorAnalysis({ applicationId, auditRunId }); PerformancesUtils.endMeasure('GreenHostingAnalysisWorker-startAuditorAnalysis'); AppLogger.info('******************** Green Hosting Audit completed ********************'); diff --git a/v6y-apps/bfb-dynamic-auditor/src/workers/LighthouseAnalysisWorker.ts b/v6y-apps/bfb-dynamic-auditor/src/workers/LighthouseAnalysisWorker.ts index aa6f1981..e0b64c5d 100644 --- a/v6y-apps/bfb-dynamic-auditor/src/workers/LighthouseAnalysisWorker.ts +++ b/v6y-apps/bfb-dynamic-auditor/src/workers/LighthouseAnalysisWorker.ts @@ -7,9 +7,10 @@ import LighthouseAuditor from '../auditors/lighthouse/LighthouseAuditor.ts'; AppLogger.info('******************** Starting background Audit **************************'); try { - const { applicationId, workspaceFolder, chromeExecutablePath } = workerData || {}; + const { applicationId, workspaceFolder, chromeExecutablePath, auditRunId } = workerData || {}; AppLogger.info(`[LighthouseAnalysisWorker] applicationId: ${applicationId}`); AppLogger.info(`[LighthouseAnalysisWorker] workspaceFolder: ${workspaceFolder}`); + AppLogger.info(`[LighthouseAnalysisWorker] auditRunId: ${auditRunId}`); // *********************************************** Database Configuration and Connection *********************************************** await DataBaseManager.connect(); @@ -18,6 +19,7 @@ try { PerformancesUtils.startMeasure('LighthouseAnalysisWorker-startAuditorAnalysis'); await LighthouseAuditor.startAuditorAnalysis({ applicationId, + auditRunId, browserPath: chromeExecutablePath, }); PerformancesUtils.endMeasure('LighthouseAnalysisWorker-startAuditorAnalysis'); diff --git a/v6y-apps/bfb-dynamic-auditor/src/workers/SonarQubeAnalysisWorker.ts b/v6y-apps/bfb-dynamic-auditor/src/workers/SonarQubeAnalysisWorker.ts index f8361bef..646ad2fa 100644 --- a/v6y-apps/bfb-dynamic-auditor/src/workers/SonarQubeAnalysisWorker.ts +++ b/v6y-apps/bfb-dynamic-auditor/src/workers/SonarQubeAnalysisWorker.ts @@ -7,15 +7,16 @@ import SonarQubeAuditorManager from '../auditors/SonarQubeAuditorManager.ts'; AppLogger.info('******************** Starting SonarQube Audit **************************'); try { - const { applicationId } = workerData || {}; + const { applicationId, auditRunId } = workerData || {}; AppLogger.info(`[SonarQubeAnalysisWorker] applicationId: ${applicationId}`); + AppLogger.info(`[SonarQubeAnalysisWorker] auditRunId: ${auditRunId}`); // *********************************************** Database Configuration and Connection *********************************************** await DataBaseManager.connect(); // *********************************************** Audit Configuration and Launch *********************************************** PerformancesUtils.startMeasure('SonarQubeAnalysisWorker-startAuditorAnalysis'); - await SonarQubeAuditorManager.startAuditorAnalysis({ applicationId }); + await SonarQubeAuditorManager.startAuditorAnalysis({ applicationId, auditRunId }); PerformancesUtils.endMeasure('SonarQubeAnalysisWorker-startAuditorAnalysis'); AppLogger.info( diff --git a/v6y-apps/bfb-main-analyzer/src/managers/ApplicationManager.ts b/v6y-apps/bfb-main-analyzer/src/managers/ApplicationManager.ts index 71410dce..d2957d56 100644 --- a/v6y-apps/bfb-main-analyzer/src/managers/ApplicationManager.ts +++ b/v6y-apps/bfb-main-analyzer/src/managers/ApplicationManager.ts @@ -1,4 +1,10 @@ -import { AppLogger, ApplicationProvider, ApplicationType, RepositoryApi } from '@v6y/core-logic'; +import { + AppLogger, + ApplicationProvider, + ApplicationType, + AuditRunProvider, + RepositoryApi, +} from '@v6y/core-logic'; import { buildDynamicReports, buildStaticReports } from './AuditManager.ts'; @@ -9,6 +15,8 @@ const { getRepositoryDetails, getRepositoryBranches } = RepositoryApi; * @param application */ const buildApplicationReports = async (application: ApplicationType) => { + let auditRunId: string | undefined; + try { if ( !application?.name?.length || @@ -55,26 +63,75 @@ const buildApplicationReports = async (application: ApplicationType) => { }, }); + // Create AuditRun record for this scheduled analysis + const auditRun = await AuditRunProvider.createAuditRun({ + appId: application._id!, + branch: null, // Scheduled runs analyze all branches + runStatus: 'pending', + analysisTypes: ['static', 'dynamic', 'devops'], + }); + + if (auditRun?._id) { + auditRunId = String(auditRun._id); + AppLogger.info( + `[ApplicationManager - buildApplicationReports] AuditRun created: ${auditRunId}`, + ); + + // Update status to in_progress + await AuditRunProvider.updateAuditRunStatus({ + auditRunId, + runStatus: 'in_progress', + }); + } + AppLogger.info('[ApplicationManager - buildApplicationDetails] start of static analysis'); - await buildStaticReports({ + const staticSuccess = await buildStaticReports({ application, branches: repositoryBranches, + auditRunId, }); AppLogger.info('[ApplicationManager - buildApplicationDetails] end of static analysis'); AppLogger.info('[ApplicationManager - buildApplicationDetails] start of dynamic analysis'); - await buildDynamicReports({ + const dynamicSuccess = await buildDynamicReports({ application, + auditRunId, }); AppLogger.info('[ApplicationManager - buildApplicationDetails] end of dynamic analysis'); + // Update AuditRun status to completed + if (auditRunId && staticSuccess && dynamicSuccess) { + await AuditRunProvider.updateAuditRunStatus({ + auditRunId, + runStatus: 'completed', + completedAt: new Date(), + }); + AppLogger.info( + `[ApplicationManager - buildApplicationReports] AuditRun completed: ${auditRunId}`, + ); + } + return true; } catch (error) { AppLogger.error('[ApplicationManager - buildApplicationDetails] error: ', error); + + // Update AuditRun status to error if it was created + if (auditRunId) { + await AuditRunProvider.updateAuditRunStatus({ + auditRunId, + runStatus: 'error', + errorMessage: String(error), + }).catch((updateErr) => + AppLogger.warn( + `[ApplicationManager - buildApplicationReports] Failed to update error status: ${updateErr}`, + ), + ); + } + return false; } }; diff --git a/v6y-apps/bfb-main-analyzer/src/managers/AuditManager.ts b/v6y-apps/bfb-main-analyzer/src/managers/AuditManager.ts index 50d740e3..31cc4915 100644 --- a/v6y-apps/bfb-main-analyzer/src/managers/AuditManager.ts +++ b/v6y-apps/bfb-main-analyzer/src/managers/AuditManager.ts @@ -12,11 +12,17 @@ const { dynamicAuditorApiPath, devopsAuditorApiPath } = currentConfig || {}; * Builds the static reports. * @param application * @param branches + * @param auditRunId */ -export const buildStaticReports = async ({ application, branches }: BuildApplicationParams) => { +export const buildStaticReports = async ({ + application, + branches, + auditRunId, +}: BuildApplicationParams) => { try { AppLogger.info('[ApplicationManager - buildStaticReports] branches: ', branches?.length); AppLogger.info('[ApplicationManager - buildStaticReports] application: ', application); + AppLogger.info('[ApplicationManager - buildStaticReports] auditRunId: ', auditRunId); if (!branches?.length || !application) { return false; @@ -28,6 +34,7 @@ export const buildStaticReports = async ({ application, branches }: BuildApplica await buildApplicationDetailsByBranch({ application, branch, + auditRunId, }); } @@ -41,14 +48,19 @@ export const buildStaticReports = async ({ application, branches }: BuildApplica /** * Builds the dynamic reports. * @param application + * @param auditRunId */ -export const buildDynamicReports = async ({ application }: BuildApplicationParams) => { +export const buildDynamicReports = async ({ application, auditRunId }: BuildApplicationParams) => { AppLogger.info('[ApplicationManager - buildDynamicReports] application: ', application?._id); + AppLogger.info('[ApplicationManager - buildDynamicReports] auditRunId: ', auditRunId); if (!application) { return false; } + let devOpsSuccess = true; + let dynamicSuccess = true; + AppLogger.info( '[ApplicationManager - buildDynamicReports] devopsAuditorApiPath: ', devopsAuditorApiPath, @@ -60,6 +72,7 @@ export const buildDynamicReports = async ({ application }: BuildApplicationParam headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ applicationId: application?._id, + auditRunId, }), }); @@ -67,6 +80,7 @@ export const buildDynamicReports = async ({ application }: BuildApplicationParam throw new Error(`HTTP ${response.status}: ${response.statusText}`); } } catch (error) { + devOpsSuccess = false; AppLogger.info( `[ApplicationManager - buildDynamicReports - devOpsAuditor] error: ${String(error)}`, ); @@ -83,6 +97,7 @@ export const buildDynamicReports = async ({ application }: BuildApplicationParam headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ applicationId: application?._id, + auditRunId, workspaceFolder: null, }), }); @@ -91,10 +106,11 @@ export const buildDynamicReports = async ({ application }: BuildApplicationParam throw new Error(`HTTP ${response.status}: ${response.statusText}`); } } catch (error) { + dynamicSuccess = false; AppLogger.info( `[ApplicationManager - buildDynamicReports - dynamicAuditor] error: ${String(error)}`, ); } - return true; + return devOpsSuccess && dynamicSuccess; }; diff --git a/v6y-apps/bfb-main-analyzer/src/managers/BranchManager.ts b/v6y-apps/bfb-main-analyzer/src/managers/BranchManager.ts index 0c3a8d6d..5ddc8cae 100644 --- a/v6y-apps/bfb-main-analyzer/src/managers/BranchManager.ts +++ b/v6y-apps/bfb-main-analyzer/src/managers/BranchManager.ts @@ -53,6 +53,7 @@ export const checkForStaticAudits = async (applicationId: number | undefined): P export const buildApplicationBackendByBranch = async ({ applicationId, workspaceFolder, + auditRunId, }: BuildApplicationParams) => { try { AppLogger.info( @@ -63,6 +64,10 @@ export const buildApplicationBackendByBranch = async ({ '[ApplicationManager - buildApplicationBackendByBranch] workspaceFolder: ', workspaceFolder, ); + AppLogger.info( + '[ApplicationManager - buildApplicationBackendByBranch] auditRunId: ', + auditRunId, + ); return true; } catch (error) { AppLogger.error(`[ApplicationManager - buildApplicationBackendByBranch] error: ${error}`); @@ -74,10 +79,12 @@ export const buildApplicationBackendByBranch = async ({ * Builds the application frontend by branch. * @param applicationId * @param workspaceFolder + * @param auditRunId */ export const buildApplicationFrontendByBranch = async ({ applicationId, workspaceFolder, + auditRunId, }: BuildApplicationParams) => { AppLogger.info( '[ApplicationManager - buildApplicationFrontendByBranch] Starting static auditor for applicationId: ', @@ -87,6 +94,10 @@ export const buildApplicationFrontendByBranch = async ({ '[ApplicationManager - buildApplicationFrontendByBranch] workspaceFolder: ', workspaceFolder, ); + AppLogger.info( + '[ApplicationManager - buildApplicationFrontendByBranch] auditRunId: ', + auditRunId, + ); AppLogger.info( '[ApplicationManager - buildApplicationFrontendByBranch] staticAuditorApiPath: ', staticAuditorApiPath, @@ -96,7 +107,7 @@ export const buildApplicationFrontendByBranch = async ({ const response = await fetch(staticAuditorApiPath as string, { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ applicationId, workspaceFolder }), + body: JSON.stringify({ applicationId, workspaceFolder, auditRunId }), }); AppLogger.info( @@ -240,15 +251,21 @@ const extractBranchZip = async ( /** * Triggers the static auditors for the branch. */ -const triggerStaticAuditors = async (applicationId: number, workspaceFolder: string) => { +const triggerStaticAuditors = async ( + applicationId: number, + workspaceFolder: string, + auditRunId?: string, +) => { await buildApplicationFrontendByBranch({ applicationId, workspaceFolder, + auditRunId, }); await buildApplicationBackendByBranch({ applicationId, workspaceFolder, + auditRunId, }); }; @@ -256,10 +273,12 @@ const triggerStaticAuditors = async (applicationId: number, workspaceFolder: str * Builds the application details by branch. * @param application * @param branch + * @param auditRunId */ export const buildApplicationDetailsByBranch = async ({ application, branch, + auditRunId, }: BuildApplicationParams) => { try { AppLogger.info( @@ -267,6 +286,10 @@ export const buildApplicationDetailsByBranch = async ({ application, ); AppLogger.info('[ApplicationManager - buildApplicationDetailsByBranch] branch: ', branch); + AppLogger.info( + '[ApplicationManager - buildApplicationDetailsByBranch] auditRunId: ', + auditRunId, + ); if (!application) return false; @@ -300,7 +323,7 @@ export const buildApplicationDetailsByBranch = async ({ return false; } - await triggerStaticAuditors(application._id, workspaceFolder!); + await triggerStaticAuditors(application._id, workspaceFolder!, auditRunId); return true; } catch (error) { diff --git a/v6y-apps/bfb-main-analyzer/src/managers/Types.ts b/v6y-apps/bfb-main-analyzer/src/managers/Types.ts index 667a42e8..462d87cd 100644 --- a/v6y-apps/bfb-main-analyzer/src/managers/Types.ts +++ b/v6y-apps/bfb-main-analyzer/src/managers/Types.ts @@ -10,4 +10,5 @@ export interface BuildApplicationParams { application?: ApplicationType; branch?: BuildApplicationBranchParams; branches?: BuildApplicationBranchParams[]; + auditRunId?: string; } diff --git a/v6y-apps/bfb-static-auditor/src/auditors/StaticAuditorManager.ts b/v6y-apps/bfb-static-auditor/src/auditors/StaticAuditorManager.ts index 65734d74..e659445b 100644 --- a/v6y-apps/bfb-static-auditor/src/auditors/StaticAuditorManager.ts +++ b/v6y-apps/bfb-static-auditor/src/auditors/StaticAuditorManager.ts @@ -6,19 +6,25 @@ import { AuditCommonsType } from './types/AuditCommonsType.ts'; const { forkWorker } = WorkerHelper; const { currentConfig } = ServerConfig; -const startStaticAudit = async ({ applicationId, workspaceFolder }: AuditCommonsType) => { +const startStaticAudit = async ({ + applicationId, + workspaceFolder, + auditRunId, +}: AuditCommonsType) => { try { AppLogger.info('[StaticAuditorManager - startStaticAudit] applicationId: ', applicationId); AppLogger.info( '[StaticAuditorManager - startStaticAudit] workspaceFolder: ', workspaceFolder, ); + AppLogger.info('[StaticAuditorManager - startStaticAudit] auditRunId: ', auditRunId); // Start audits in a worker thread to prevent blocking the main thread const workerConfig = { ...currentConfig, applicationId, workspaceFolder, + auditRunId, } as WorkerOptions; await forkWorker('./src/workers/CodeQualityAnalysisWorker.ts', workerConfig); diff --git a/v6y-apps/bfb-static-auditor/src/auditors/bundle-analyzer/BundleAnalyzeAuditor.ts b/v6y-apps/bfb-static-auditor/src/auditors/bundle-analyzer/BundleAnalyzeAuditor.ts index f1b0e0aa..084129bf 100644 --- a/v6y-apps/bfb-static-auditor/src/auditors/bundle-analyzer/BundleAnalyzeAuditor.ts +++ b/v6y-apps/bfb-static-auditor/src/auditors/bundle-analyzer/BundleAnalyzeAuditor.ts @@ -11,7 +11,11 @@ import BundleAnalyzeUtils from './BundleAnalyzeUtils.ts'; const { startBundleAnalyzeReports } = BundleAnalyzeUtils; -const startAuditorAnalysis = async ({ applicationId, workspaceFolder }: AuditCommonsType) => { +const startAuditorAnalysis = async ({ + applicationId, + workspaceFolder, + auditRunId, +}: AuditCommonsType) => { try { AppLogger.info( `[BundleAnalyzeAuditor - startAuditorAnalysis] applicationId: ${applicationId}`, @@ -19,6 +23,7 @@ const startAuditorAnalysis = async ({ applicationId, workspaceFolder }: AuditCom AppLogger.info( `[BundleAnalyzeAuditor - startAuditorAnalysis] workspaceFolder: ${workspaceFolder}`, ); + AppLogger.info(`[BundleAnalyzeAuditor - startAuditorAnalysis] auditRunId: ${auditRunId}`); if (applicationId === undefined || !workspaceFolder?.length) { return false; @@ -26,6 +31,15 @@ const startAuditorAnalysis = async ({ applicationId, workspaceFolder }: AuditCom const auditReports = await startBundleAnalyzeReports({ workspaceFolder, applicationId }); + // Add auditRunId to each report + if (auditRunId) { + const auditRunIdNum = + typeof auditRunId === 'string' ? parseInt(auditRunId, 10) : auditRunId; + auditReports.forEach((audit) => { + audit.auditRunId = auditRunIdNum; + }); + } + await AuditProvider.insertAuditList(auditReports); AppLogger.info( `[BundleAnalyzeAuditor - startAuditorAnalysis] audit reports inserted successfully`, diff --git a/v6y-apps/bfb-static-auditor/src/auditors/code-complexity/CodeComplexityAuditor.ts b/v6y-apps/bfb-static-auditor/src/auditors/code-complexity/CodeComplexityAuditor.ts index 817d8008..2f123c13 100644 --- a/v6y-apps/bfb-static-auditor/src/auditors/code-complexity/CodeComplexityAuditor.ts +++ b/v6y-apps/bfb-static-auditor/src/auditors/code-complexity/CodeComplexityAuditor.ts @@ -5,7 +5,11 @@ import CodeComplexityUtils from './CodeComplexityUtils.ts'; const { formatCodeComplexityReports } = CodeComplexityUtils; -const startAuditorAnalysis = async ({ applicationId, workspaceFolder }: AuditCommonsType) => { +const startAuditorAnalysis = async ({ + applicationId, + workspaceFolder, + auditRunId, +}: AuditCommonsType) => { try { AppLogger.info( `[CodeComplexityAuditor - startAuditorAnalysis] applicationId: ${applicationId}`, @@ -13,6 +17,7 @@ const startAuditorAnalysis = async ({ applicationId, workspaceFolder }: AuditCom AppLogger.info( `[CodeComplexityAuditor - startAuditorAnalysis] workspaceFolder: ${workspaceFolder}`, ); + AppLogger.info(`[CodeComplexityAuditor - startAuditorAnalysis] auditRunId: ${auditRunId}`); if (applicationId === undefined || !workspaceFolder?.length) { return false; @@ -42,6 +47,15 @@ const startAuditorAnalysis = async ({ applicationId, workspaceFolder }: AuditCom return false; } + // Add auditRunId to each report + if (auditRunId) { + const auditRunIdNum = + typeof auditRunId === 'string' ? parseInt(auditRunId, 10) : auditRunId; + auditReports.forEach((audit) => { + audit.auditRunId = auditRunIdNum; + }); + } + await AuditProvider.insertAuditList(auditReports); AppLogger.info( diff --git a/v6y-apps/bfb-static-auditor/src/auditors/code-coupling/CodeCouplingAuditor.ts b/v6y-apps/bfb-static-auditor/src/auditors/code-coupling/CodeCouplingAuditor.ts index d3c7983e..e7b84874 100644 --- a/v6y-apps/bfb-static-auditor/src/auditors/code-coupling/CodeCouplingAuditor.ts +++ b/v6y-apps/bfb-static-auditor/src/auditors/code-coupling/CodeCouplingAuditor.ts @@ -10,7 +10,11 @@ const { formatCodeCouplingReports } = CodeCouplingUtils; * @param applicationId * @param workspaceFolder */ -const startAuditorAnalysis = async ({ applicationId, workspaceFolder }: AuditCommonsType) => { +const startAuditorAnalysis = async ({ + applicationId, + workspaceFolder, + auditRunId, +}: AuditCommonsType) => { try { AppLogger.info( `[CodeCouplingAuditor - startAuditorAnalysis] applicationId: ${applicationId}`, @@ -18,6 +22,7 @@ const startAuditorAnalysis = async ({ applicationId, workspaceFolder }: AuditCom AppLogger.info( `[CodeCouplingAuditor - startAuditorAnalysis] workspaceFolder: ${workspaceFolder}`, ); + AppLogger.info(`[CodeCouplingAuditor - startAuditorAnalysis] auditRunId: ${auditRunId}`); if (applicationId === undefined || !workspaceFolder?.length) { return false; @@ -44,6 +49,15 @@ const startAuditorAnalysis = async ({ applicationId, workspaceFolder }: AuditCom workspaceFolder, }); + // Add auditRunId to each report + if (auditRunId) { + const auditRunIdNum = + typeof auditRunId === 'string' ? parseInt(auditRunId, 10) : auditRunId; + auditReports.forEach((audit) => { + audit.auditRunId = auditRunIdNum; + }); + } + await AuditProvider.insertAuditList(auditReports); AppLogger.info( diff --git a/v6y-apps/bfb-static-auditor/src/auditors/code-duplication/CodeDuplicationAuditor.ts b/v6y-apps/bfb-static-auditor/src/auditors/code-duplication/CodeDuplicationAuditor.ts index f2e7b7e0..665ab026 100644 --- a/v6y-apps/bfb-static-auditor/src/auditors/code-duplication/CodeDuplicationAuditor.ts +++ b/v6y-apps/bfb-static-auditor/src/auditors/code-duplication/CodeDuplicationAuditor.ts @@ -14,7 +14,11 @@ const { defaultOptions, formatCodeDuplicationReports } = CodeDuplicationUtils; * @param applicationId * @param workspaceFolder */ -const startAuditorAnalysis = async ({ applicationId, workspaceFolder }: AuditCommonsType) => { +const startAuditorAnalysis = async ({ + applicationId, + workspaceFolder, + auditRunId, +}: AuditCommonsType) => { try { AppLogger.info( `[CodeDuplicationAuditor - startAuditorAnalysis] applicationId: ${applicationId}`, @@ -22,6 +26,9 @@ const startAuditorAnalysis = async ({ applicationId, workspaceFolder }: AuditCom AppLogger.info( `[CodeDuplicationAuditor - startAuditorAnalysis] workspaceFolder: ${workspaceFolder}`, ); + AppLogger.info( + `[CodeDuplicationAuditor - startAuditorAnalysis] auditRunId: ${auditRunId}`, + ); if (applicationId === undefined || !workspaceFolder?.length) { return false; @@ -98,6 +105,15 @@ const startAuditorAnalysis = async ({ applicationId, workspaceFolder }: AuditCom duplicationFiles: jscpdFileAnalysisResultJson?.duplicates, }); + // Add auditRunId to each report + if (auditRunId) { + const auditRunIdNum = + typeof auditRunId === 'string' ? parseInt(auditRunId, 10) : auditRunId; + auditReports.forEach((audit) => { + audit.auditRunId = auditRunIdNum; + }); + } + await AuditProvider.insertAuditList(auditReports); AppLogger.info( diff --git a/v6y-apps/bfb-static-auditor/src/auditors/code-modularity/CodeModularityAuditor.ts b/v6y-apps/bfb-static-auditor/src/auditors/code-modularity/CodeModularityAuditor.ts index 4ff2474e..859ff498 100644 --- a/v6y-apps/bfb-static-auditor/src/auditors/code-modularity/CodeModularityAuditor.ts +++ b/v6y-apps/bfb-static-auditor/src/auditors/code-modularity/CodeModularityAuditor.ts @@ -30,6 +30,7 @@ const { const startAuditorAnalysis = async ({ applicationId, workspaceFolder, + auditRunId, }: CodeModularityAuditType) => { try { AppLogger.info( @@ -38,6 +39,7 @@ const startAuditorAnalysis = async ({ AppLogger.info( `[CodeModularityAuditor - startAuditorAnalysis] workspaceFolder: ${workspaceFolder}`, ); + AppLogger.info(`[CodeModularityAuditor - startAuditorAnalysis] auditRunId: ${auditRunId}`); if (applicationId === undefined || !workspaceFolder?.length) { return false; @@ -149,6 +151,15 @@ const startAuditorAnalysis = async ({ }, }); + // Add auditRunId to each report + if (auditRunId) { + const auditRunIdNum = + typeof auditRunId === 'string' ? parseInt(auditRunId, 10) : auditRunId; + auditReports.forEach((audit) => { + audit.auditRunId = auditRunIdNum; + }); + } + await AuditProvider.insertAuditList(auditReports); AppLogger.info( diff --git a/v6y-apps/bfb-static-auditor/src/auditors/code-security/CodeSecurityAuditor.ts b/v6y-apps/bfb-static-auditor/src/auditors/code-security/CodeSecurityAuditor.ts index c8da16e1..e4d8211d 100644 --- a/v6y-apps/bfb-static-auditor/src/auditors/code-security/CodeSecurityAuditor.ts +++ b/v6y-apps/bfb-static-auditor/src/auditors/code-security/CodeSecurityAuditor.ts @@ -10,7 +10,11 @@ const { formatCodeModularityReports } = CodeSecurityUtils; * @param applicationId * @param workspaceFolder */ -const startAuditorAnalysis = async ({ applicationId, workspaceFolder }: AuditCommonsType) => { +const startAuditorAnalysis = async ({ + applicationId, + workspaceFolder, + auditRunId, +}: AuditCommonsType) => { try { AppLogger.info( `[CodeSecurityAuditor - startAuditorAnalysis] applicationId: ${applicationId}`, @@ -18,6 +22,7 @@ const startAuditorAnalysis = async ({ applicationId, workspaceFolder }: AuditCom AppLogger.info( `[CodeSecurityAuditor - startAuditorAnalysis] workspaceFolder: ${workspaceFolder}`, ); + AppLogger.info(`[CodeSecurityAuditor - startAuditorAnalysis] auditRunId: ${auditRunId}`); if (applicationId === undefined || !workspaceFolder?.length) { return false; @@ -43,6 +48,15 @@ const startAuditorAnalysis = async ({ applicationId, workspaceFolder }: AuditCom workspaceFolder, }); + // Add auditRunId to each report + if (auditRunId) { + const auditRunIdNum = + typeof auditRunId === 'string' ? parseInt(auditRunId, 10) : auditRunId; + securityAuditReports.forEach((audit) => { + audit.auditRunId = auditRunIdNum; + }); + } + await AuditProvider.insertAuditList(securityAuditReports); AppLogger.info( diff --git a/v6y-apps/bfb-static-auditor/src/auditors/dependencies-auditor/DependenciesAuditor.ts b/v6y-apps/bfb-static-auditor/src/auditors/dependencies-auditor/DependenciesAuditor.ts index 75100d86..07c99fde 100644 --- a/v6y-apps/bfb-static-auditor/src/auditors/dependencies-auditor/DependenciesAuditor.ts +++ b/v6y-apps/bfb-static-auditor/src/auditors/dependencies-auditor/DependenciesAuditor.ts @@ -10,7 +10,11 @@ const { formatDependenciesReports } = DependenciesUtils; * @param applicationId * @param workspaceFolder */ -const startAuditorAnalysis = async ({ applicationId, workspaceFolder }: AuditCommonsType) => { +const startAuditorAnalysis = async ({ + applicationId, + workspaceFolder, + auditRunId, +}: AuditCommonsType) => { try { AppLogger.info( `[DependenciesAuditor - startAuditorAnalysis] applicationId: ${applicationId}`, @@ -18,6 +22,7 @@ const startAuditorAnalysis = async ({ applicationId, workspaceFolder }: AuditCom AppLogger.info( `[DependenciesAuditor - startAuditorAnalysis] workspaceFolder: ${workspaceFolder}`, ); + AppLogger.info(`[DependenciesAuditor - startAuditorAnalysis] auditRunId: ${auditRunId}`); if (applicationId === undefined || !workspaceFolder?.length) { return false; diff --git a/v6y-apps/bfb-static-auditor/src/auditors/ecological-impact/CreedengoAuditor.ts b/v6y-apps/bfb-static-auditor/src/auditors/ecological-impact/CreedengoAuditor.ts index c54923a4..21357379 100644 --- a/v6y-apps/bfb-static-auditor/src/auditors/ecological-impact/CreedengoAuditor.ts +++ b/v6y-apps/bfb-static-auditor/src/auditors/ecological-impact/CreedengoAuditor.ts @@ -10,7 +10,11 @@ const { formatCreedengoReports } = CreedengoUtils; * @param applicationId * @param workspaceFolder */ -const startAuditorAnalysis = async ({ applicationId, workspaceFolder }: CreedengoAuditType) => { +const startAuditorAnalysis = async ({ + applicationId, + workspaceFolder, + auditRunId, +}: CreedengoAuditType) => { try { AppLogger.info( `[CreedengoAuditor - startAuditorAnalysis] Starting ecological impact analysis`, @@ -21,6 +25,7 @@ const startAuditorAnalysis = async ({ applicationId, workspaceFolder }: Creedeng AppLogger.info( `[CreedengoAuditor - startAuditorAnalysis] workspaceFolder: ${workspaceFolder}`, ); + AppLogger.info(`[CreedengoAuditor - startAuditorAnalysis] auditRunId: ${auditRunId}`); if (applicationId === undefined || !workspaceFolder?.length) { AppLogger.warn( @@ -58,6 +63,15 @@ const startAuditorAnalysis = async ({ applicationId, workspaceFolder }: Creedeng return true; } + // Add auditRunId to each report + if (auditRunId) { + const auditRunIdNum = + typeof auditRunId === 'string' ? parseInt(auditRunId, 10) : auditRunId; + creedengoAuditReports.forEach((audit) => { + audit.auditRunId = auditRunIdNum; + }); + } + await AuditProvider.insertAuditList(creedengoAuditReports); AppLogger.info( diff --git a/v6y-apps/bfb-static-auditor/src/auditors/types/AuditCommonsType.ts b/v6y-apps/bfb-static-auditor/src/auditors/types/AuditCommonsType.ts index 2b170bc4..93740447 100644 --- a/v6y-apps/bfb-static-auditor/src/auditors/types/AuditCommonsType.ts +++ b/v6y-apps/bfb-static-auditor/src/auditors/types/AuditCommonsType.ts @@ -4,4 +4,5 @@ export interface AuditCommonsType { applicationId?: number; application?: ApplicationType; workspaceFolder?: string; + auditRunId?: string; } diff --git a/v6y-apps/bfb-static-auditor/src/auditors/types/CodeModularityAuditType.ts b/v6y-apps/bfb-static-auditor/src/auditors/types/CodeModularityAuditType.ts index 1b026cfb..05229adf 100644 --- a/v6y-apps/bfb-static-auditor/src/auditors/types/CodeModularityAuditType.ts +++ b/v6y-apps/bfb-static-auditor/src/auditors/types/CodeModularityAuditType.ts @@ -16,6 +16,7 @@ export interface CodeModularityAuditType { application?: ApplicationType; applicationId?: number; workspaceFolder?: string; + auditRunId?: string; modularitySummary?: { projectLouvainDetails?: { communities?: { [key: string]: number }; diff --git a/v6y-apps/bfb-static-auditor/src/auditors/types/CreedengoAuditType.ts b/v6y-apps/bfb-static-auditor/src/auditors/types/CreedengoAuditType.ts index ec62be63..76e33e4c 100644 --- a/v6y-apps/bfb-static-auditor/src/auditors/types/CreedengoAuditType.ts +++ b/v6y-apps/bfb-static-auditor/src/auditors/types/CreedengoAuditType.ts @@ -4,4 +4,5 @@ export interface CreedengoAuditType { application?: ApplicationType; applicationId?: number; workspaceFolder?: string; + auditRunId?: string; } diff --git a/v6y-apps/bfb-static-auditor/src/routes/StaticAuditorRouter.ts b/v6y-apps/bfb-static-auditor/src/routes/StaticAuditorRouter.ts index e7e9db44..a480fdcb 100644 --- a/v6y-apps/bfb-static-auditor/src/routes/StaticAuditorRouter.ts +++ b/v6y-apps/bfb-static-auditor/src/routes/StaticAuditorRouter.ts @@ -12,12 +12,13 @@ StaticAuditorRouter.post('/start-static-auditor.json', async (request, response) try { AppLogger.debug('[StaticAuditorRouter] Entering service: [start-static-auditor]'); - const { applicationId, workspaceFolder } = request.body || {}; + const { applicationId, workspaceFolder, auditRunId } = request.body || {}; // ********************************************** Start Audits *********************************************** const auditsStartedSuccessfully = await StaticAuditorManager.startStaticAudit({ applicationId, workspaceFolder, + auditRunId, }); // Wait for audits to potentially complete // ********************************************** Server Response *********************************************** diff --git a/v6y-apps/bfb-static-auditor/src/workers/BundleAnalyzeWorker.ts b/v6y-apps/bfb-static-auditor/src/workers/BundleAnalyzeWorker.ts index 89a189b4..88e95656 100644 --- a/v6y-apps/bfb-static-auditor/src/workers/BundleAnalyzeWorker.ts +++ b/v6y-apps/bfb-static-auditor/src/workers/BundleAnalyzeWorker.ts @@ -7,9 +7,10 @@ import BundleAnalyzeAuditor from '../auditors/bundle-analyzer/BundleAnalyzeAudit AppLogger.info('******************** Starting background Audit **************************'); try { - const { applicationId, workspaceFolder } = workerData || {}; + const { applicationId, workspaceFolder, auditRunId } = workerData || {}; AppLogger.info(`[BundleAnalyzeWorker] applicationId: ${applicationId}`); AppLogger.info(`[BundleAnalyzeWorker] workspaceFolder: ${workspaceFolder}`); + AppLogger.info(`[BundleAnalyzeWorker] auditRunId: ${auditRunId}`); // *********************************************** Database Configuration and Connection *********************************************** await DataBaseManager.connect(); @@ -19,6 +20,7 @@ try { await BundleAnalyzeAuditor.startAuditorAnalysis({ applicationId, workspaceFolder, + auditRunId, }); PerformancesUtils.endMeasure('BundleAnalyzeWorker-startAuditorAnalysis'); diff --git a/v6y-apps/bfb-static-auditor/src/workers/CodeQualityAnalysisWorker.ts b/v6y-apps/bfb-static-auditor/src/workers/CodeQualityAnalysisWorker.ts index 0ac51244..e285e950 100644 --- a/v6y-apps/bfb-static-auditor/src/workers/CodeQualityAnalysisWorker.ts +++ b/v6y-apps/bfb-static-auditor/src/workers/CodeQualityAnalysisWorker.ts @@ -12,9 +12,10 @@ import CreedengoAuditor from '../auditors/ecological-impact/CreedengoAuditor.ts' AppLogger.info('******************** Starting background Audit **************************'); try { - const { applicationId, workspaceFolder } = workerData || {}; + const { applicationId, workspaceFolder, auditRunId } = workerData || {}; AppLogger.info(`[CodeQualityAnalysisWorker] applicationId: ${applicationId}`); AppLogger.info(`[CodeQualityAnalysisWorker] workspaceFolder: ${workspaceFolder}`); + AppLogger.info(`[CodeQualityAnalysisWorker] auditRunId: ${auditRunId}`); await DataBaseManager.connect(); @@ -22,26 +23,32 @@ try { await CodeComplexityAuditor.startAuditorAnalysis({ applicationId, workspaceFolder, + auditRunId, }); await CodeCouplingAuditor.startAuditorAnalysis({ applicationId, workspaceFolder, + auditRunId, }); await CodeDuplicationAuditor.startAuditorAnalysis({ applicationId, workspaceFolder, + auditRunId, }); await CodeModularityAuditor.startAuditorAnalysis({ applicationId, workspaceFolder, + auditRunId, }); await CodeSecurityAuditor.startAuditorAnalysis({ applicationId, workspaceFolder, + auditRunId, }); await CreedengoAuditor.startAuditorAnalysis({ applicationId, workspaceFolder, + auditRunId, }); PerformancesUtils.endMeasure('CodeQualityAnalysisWorker-startAuditorAnalysis'); diff --git a/v6y-apps/bfb-static-auditor/src/workers/DependenciesAnalysisWorker.ts b/v6y-apps/bfb-static-auditor/src/workers/DependenciesAnalysisWorker.ts index 4caf89e7..741d7a9d 100644 --- a/v6y-apps/bfb-static-auditor/src/workers/DependenciesAnalysisWorker.ts +++ b/v6y-apps/bfb-static-auditor/src/workers/DependenciesAnalysisWorker.ts @@ -7,9 +7,10 @@ import DependenciesAuditor from '../auditors/dependencies-auditor/DependenciesAu AppLogger.info('******************** Starting background Audit **************************'); try { - const { applicationId, workspaceFolder } = workerData || {}; + const { applicationId, workspaceFolder, auditRunId } = workerData || {}; AppLogger.info(`[DependenciesAnalysisWorker] applicationId: ${applicationId}`); AppLogger.info(`[DependenciesAnalysisWorker] workspaceFolder: ${workspaceFolder}`); + AppLogger.info(`[DependenciesAnalysisWorker] auditRunId: ${auditRunId}`); // *********************************************** Database Configuration and Connection *********************************************** await DataBaseManager.connect(); @@ -19,6 +20,7 @@ try { await DependenciesAuditor.startAuditorAnalysis({ applicationId, workspaceFolder, + auditRunId, }); PerformancesUtils.endMeasure('DependenciesAnalysisWorker-startAuditorAnalysis'); diff --git a/v6y-apps/bff/src/resolvers/application/ApplicationMutations.ts b/v6y-apps/bff/src/resolvers/application/ApplicationMutations.ts index 28be3d1b..f2b9b72a 100644 --- a/v6y-apps/bff/src/resolvers/application/ApplicationMutations.ts +++ b/v6y-apps/bff/src/resolvers/application/ApplicationMutations.ts @@ -2,9 +2,61 @@ import { AppLogger, ApplicationInputType, ApplicationProvider, + AuditRunProvider, SearchQueryType, } from '@v6y/core-logic'; +/** + * Trigger static auditor for application + */ +const triggerStaticAuditor = async (applicationId: number, auditRunId: string) => { + try { + const staticAuditorUrl = + process.env.V6Y_STATIC_AUDITOR_API_PATH || 'http://bfb-static-auditor:3001/audits'; + await fetch(staticAuditorUrl, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ applicationId, auditRunId }), + }); + } catch (err) { + AppLogger.warn(`[AppMutations - triggerApplicationAnalysis] Static auditor error: ${err}`); + } +}; + +/** + * Trigger dynamic auditor for application + */ +const triggerDynamicAuditor = async (applicationId: number, auditRunId: string) => { + try { + const dynamicAuditorUrl = + process.env.V6Y_DYNAMIC_AUDITOR_API_PATH || 'http://bfb-dynamic-auditor:3002/audits'; + await fetch(dynamicAuditorUrl, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ applicationId, auditRunId, workspaceFolder: null }), + }); + } catch (err) { + AppLogger.warn(`[AppMutations - triggerApplicationAnalysis] Dynamic auditor error: ${err}`); + } +}; + +/** + * Trigger devops auditor for application + */ +const triggerDevopsAuditor = async (applicationId: number, auditRunId: string) => { + try { + const devopsAuditorUrl = + process.env.V6Y_DEVOPS_AUDITOR_API_PATH || 'http://bfb-devops-auditor:3003/audits'; + await fetch(devopsAuditorUrl, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ applicationId, auditRunId }), + }); + } catch (err) { + AppLogger.warn(`[AppMutations - triggerApplicationAnalysis] DevOps auditor error: ${err}`); + } +}; + /** * Create or edit application * @param _ @@ -166,9 +218,176 @@ const deleteApplication = async (_: unknown, params: { input: SearchQueryType }) } }; +/** + * Trigger application analysis + * @param _ + * @param params + */ +const triggerApplicationAnalysis = async ( + _: unknown, + params: { + input: { + applicationId: number; + branch?: string; + analysisTypes: string[]; + }; + }, +) => { + let auditRunId: string | undefined; + + try { + const { applicationId, branch, analysisTypes } = params?.input || {}; + + if (!applicationId || !analysisTypes || analysisTypes.length === 0) { + AppLogger.error( + '[AppMutations - triggerApplicationAnalysis] Missing required parameters', + ); + return { + success: false, + message: 'Missing applicationId or analysisTypes', + applicationId, + branch, + auditRun: null, + }; + } + + AppLogger.info( + `[AppMutations - triggerApplicationAnalysis] applicationId: ${applicationId}, branch: ${branch}, analysisTypes: ${analysisTypes.join(',')}`, + ); + + // Verify application exists + const application = await ApplicationProvider.getApplicationDetailsInfoByParams({ + _id: applicationId, + }); + + if (!application) { + AppLogger.error( + `[AppMutations - triggerApplicationAnalysis] Application not found: ${applicationId}`, + ); + return { + success: false, + message: 'Application not found', + applicationId, + branch, + auditRun: null, + }; + } + + // Create audit run record + const auditRun = await AuditRunProvider.createAuditRun({ + appId: applicationId, + branch: branch || null, + runStatus: 'pending', + analysisTypes, + }); + + if (!auditRun) { + AppLogger.error( + '[AppMutations - triggerApplicationAnalysis] Failed to create audit run', + ); + return { + success: false, + message: 'Failed to create audit run', + applicationId, + branch, + auditRun: null, + }; + } + + auditRunId = auditRun._id; + AppLogger.info( + `[AppMutations - triggerApplicationAnalysis] Audit run created: ${auditRunId}`, + ); + + // Update status to in_progress + await AuditRunProvider.updateAuditRunStatus({ + auditRunId, + runStatus: 'in_progress', + }); + + // Trigger async analysis job via API calls + (async () => { + try { + const branchesToAnalyze = branch + ? [{ name: branch }] + : application?.repo?.allBranches?.map((b: string) => ({ name: b })) || []; + + // Trigger static auditor if included + if (analysisTypes.includes('static') && branchesToAnalyze.length > 0) { + AppLogger.info( + `[AppMutations - triggerApplicationAnalysis] Triggering static auditor for app ${applicationId}`, + ); + await triggerStaticAuditor(applicationId, auditRunId); + } + + // Trigger dynamic auditor if included + if (analysisTypes.includes('dynamic')) { + AppLogger.info( + `[AppMutations - triggerApplicationAnalysis] Triggering dynamic auditor for app ${applicationId}`, + ); + await triggerDynamicAuditor(applicationId, auditRunId); + } + + // Trigger devops auditor if included + if (analysisTypes.includes('devops')) { + AppLogger.info( + `[AppMutations - triggerApplicationAnalysis] Triggering devops auditor for app ${applicationId}`, + ); + await triggerDevopsAuditor(applicationId, auditRunId); + } + } catch (asyncError) { + AppLogger.error( + `[AppMutations - triggerApplicationAnalysis] Async analysis job error: ${asyncError}`, + ); + await AuditRunProvider.updateAuditRunStatus({ + auditRunId: auditRunId!, + runStatus: 'error', + errorMessage: String(asyncError), + }).catch((updateErr) => + AppLogger.warn( + `[AppMutations - triggerApplicationAnalysis] Failed to update error status: ${updateErr}`, + ), + ); + } + })(); + + return { + success: true, + message: 'Analysis triggered successfully', + applicationId, + branch, + auditRun, + }; + } catch (error) { + AppLogger.error(`[AppMutations - triggerApplicationAnalysis] error: ${error}`); + + // Try to update run status to error if it was created + if (auditRunId) { + await AuditRunProvider.updateAuditRunStatus({ + auditRunId, + runStatus: 'error', + errorMessage: String(error), + }).catch((updateErr) => + AppLogger.warn( + `[AppMutations - triggerApplicationAnalysis] Failed to update error status: ${updateErr}`, + ), + ); + } + + return { + success: false, + message: `Error triggering analysis: ${error}`, + applicationId: params?.input?.applicationId, + branch: params?.input?.branch, + auditRun: null, + }; + } +}; + const ApplicationMutations = { createOrEditApplication, deleteApplication, + triggerApplicationAnalysis, }; export default ApplicationMutations; diff --git a/v6y-apps/bff/src/resolvers/application/ApplicationQueries.ts b/v6y-apps/bff/src/resolvers/application/ApplicationQueries.ts index d6ec7e1f..fa058072 100644 --- a/v6y-apps/bff/src/resolvers/application/ApplicationQueries.ts +++ b/v6y-apps/bff/src/resolvers/application/ApplicationQueries.ts @@ -3,6 +3,7 @@ import { AppLogger, ApplicationProvider, ApplicationType, + AuditRunProvider, SearchQueryType, } from '@v6y/core-logic'; @@ -327,6 +328,161 @@ const getApplicationTotalByParams = async ( } }; +/** + * Get application audit history by params + * @param _ + * @param args + * @param user + */ +const getApplicationAuditHistoryByParams = async ( + _: unknown, + args: { _id: number; limit?: number; offset?: number }, + { user }: { user: AccountType }, +) => { + try { + const { _id, limit, offset } = args || {}; + + if (!(user.role === 'ADMIN' || user.role === 'SUPERADMIN')) { + const userApplicationsIds = user.applications || []; + if (!userApplicationsIds.includes(_id)) { + throw new Error('Unauthorized'); + } + } + + AppLogger.info( + `[ApplicationQueries - getApplicationAuditHistoryByParams] _id: ${_id}, limit: ${limit}, offset: ${offset}`, + ); + + const auditRuns = await AuditRunProvider.getAuditRunsByApplicationId(_id, limit, offset); + + AppLogger.info( + `[ApplicationQueries - getApplicationAuditHistoryByParams] auditRuns count: ${auditRuns?.length}`, + ); + + return auditRuns; + } catch (error) { + AppLogger.error( + `[ApplicationQueries - getApplicationAuditHistoryByParams] error: ${error}`, + ); + return []; + } +}; + +/** + * Get application audit history count by params + * @param _ + * @param args + * @param user + */ +const getApplicationAuditHistoryCountByParams = async ( + _: unknown, + args: { _id: number }, + { user }: { user: AccountType }, +) => { + try { + const { _id } = args || {}; + + if (!(user.role === 'ADMIN' || user.role === 'SUPERADMIN')) { + const userApplicationsIds = user.applications || []; + if (!userApplicationsIds.includes(_id)) { + throw new Error('Unauthorized'); + } + } + + AppLogger.info( + `[ApplicationQueries - getApplicationAuditHistoryCountByParams] _id: ${_id}`, + ); + + const count = await AuditRunProvider.getAuditRunsCountByApplicationId(_id); + + AppLogger.info( + `[ApplicationQueries - getApplicationAuditHistoryCountByParams] count: ${count}`, + ); + + return count; + } catch (error) { + AppLogger.error( + `[ApplicationQueries - getApplicationAuditHistoryCountByParams] error: ${error}`, + ); + return 0; + } +}; + +/** + * Get application latest audit run by params + * @param _ + * @param args + * @param user + */ +const getApplicationLatestAuditRunByParams = async ( + _: unknown, + args: { _id: number }, + { user }: { user: AccountType }, +) => { + try { + const { _id } = args || {}; + + if (!(user.role === 'ADMIN' || user.role === 'SUPERADMIN')) { + const userApplicationsIds = user.applications || []; + if (!userApplicationsIds.includes(_id)) { + throw new Error('Unauthorized'); + } + } + + AppLogger.info(`[ApplicationQueries - getApplicationLatestAuditRunByParams] _id: ${_id}`); + + const auditRun = await AuditRunProvider.getLatestAuditRun(_id); + + AppLogger.info( + `[ApplicationQueries - getApplicationLatestAuditRunByParams] auditRun: ${auditRun?._id}`, + ); + + return auditRun; + } catch (error) { + AppLogger.error( + `[ApplicationQueries - getApplicationLatestAuditRunByParams] error: ${error}`, + ); + return null; + } +}; + +/** + * Get audit run details by params + * @param _ + * @param args + * @param user + */ +const getAuditRunDetailsByParams = async ( + _: unknown, + args: { _id: number }, + { user }: { user: AccountType }, +) => { + try { + const { _id } = args || {}; + + AppLogger.info(`[ApplicationQueries - getAuditRunDetailsByParams] _id: ${_id}`); + + const auditRun = await AuditRunProvider.getAuditRunById(_id); + + // Check authorization if audit run exists and user is not ADMIN/SUPERADMIN + if (auditRun && !(user.role === 'ADMIN' || user.role === 'SUPERADMIN')) { + const userApplicationsIds = user.applications || []; + if (!userApplicationsIds.includes(auditRun.appId)) { + throw new Error('Unauthorized'); + } + } + + AppLogger.info( + `[ApplicationQueries - getAuditRunDetailsByParams] auditRun: ${auditRun?._id}`, + ); + + return auditRun; + } catch (error) { + AppLogger.error(`[ApplicationQueries - getAuditRunDetailsByParams] error: ${error}`); + return null; + } +}; + const ApplicationQueries = { getApplicationDetailsInfoByParams, getApplicationDetailsAuditReportsByParams, @@ -336,6 +492,10 @@ const ApplicationQueries = { getApplicationTotalByParams, getApplicationListByPageAndParams, getApplicationStatsByParams, + getApplicationAuditHistoryByParams, + getApplicationAuditHistoryCountByParams, + getApplicationLatestAuditRunByParams, + getAuditRunDetailsByParams, }; export default ApplicationQueries; diff --git a/v6y-apps/bff/src/types/VitalityTypes.ts b/v6y-apps/bff/src/types/VitalityTypes.ts index 44349faa..059e1130 100644 --- a/v6y-apps/bff/src/types/VitalityTypes.ts +++ b/v6y-apps/bff/src/types/VitalityTypes.ts @@ -17,7 +17,10 @@ import ApplicationDeleteOutput from './application/ApplicationDeleteOutput.ts'; import ApplicationMutationsType from './application/ApplicationMutationsType.ts'; import ApplicationQueriesType from './application/ApplicationQueriesType.ts'; import ApplicationType from './application/ApplicationType.ts'; +import TriggerApplicationAnalysisInput from './application/TriggerApplicationAnalysisInput.ts'; +import TriggerApplicationAnalysisOutput from './application/TriggerApplicationAnalysisOutput.ts'; import AuditReportType from './audit/AuditReportType.ts'; +import AuditRunType from './audit/AuditRunType.ts'; import AuditHelpCreateOrEditInput from './audit/help/AuditHelpCreateOrEditInput.ts'; import AuditHelpDeleteInput from './audit/help/AuditHelpDeleteInput.ts'; import AuditHelpDeleteOutput from './audit/help/AuditHelpDeleteOutput.ts'; @@ -91,6 +94,7 @@ const VitalityTypes = gql(` ${ApplicationType} ${AuditHelpType} ${AuditReportType} + ${AuditRunType} ${FaqType} ${NotificationType} @@ -108,6 +112,8 @@ const VitalityTypes = gql(` ${ApplicationCreateOrEditInput} ${ApplicationDeleteOutput} ${ApplicationDeleteInput} + ${TriggerApplicationAnalysisInput} + ${TriggerApplicationAnalysisOutput} ${ApplicationMutationsType} ${FaqCreateOrEditInput} diff --git a/v6y-apps/bff/src/types/application/ApplicationMutationsType.ts b/v6y-apps/bff/src/types/application/ApplicationMutationsType.ts index d608a82c..1fe7f7fa 100644 --- a/v6y-apps/bff/src/types/application/ApplicationMutationsType.ts +++ b/v6y-apps/bff/src/types/application/ApplicationMutationsType.ts @@ -2,6 +2,7 @@ const ApplicationMutationsType = ` type Mutation { createOrEditApplication(applicationInput: ApplicationCreateOrEditInput!): ApplicationType deleteApplication(input: ApplicationDeleteInput!): ApplicationDeleteOutput + triggerApplicationAnalysis(input: TriggerApplicationAnalysisInput!): TriggerApplicationAnalysisOutput } `; diff --git a/v6y-apps/bff/src/types/application/ApplicationQueriesType.ts b/v6y-apps/bff/src/types/application/ApplicationQueriesType.ts index bd42f410..e141eeba 100644 --- a/v6y-apps/bff/src/types/application/ApplicationQueriesType.ts +++ b/v6y-apps/bff/src/types/application/ApplicationQueriesType.ts @@ -8,6 +8,10 @@ const ApplicationQueriesType = ` getApplicationDetailsEvolutionsByParams(_id: Int!): [EvolutionType] getApplicationDetailsDependenciesByParams(_id: Int!): [DependencyType] getApplicationDetailsKeywordsByParams(_id: Int): [KeywordType] + getApplicationAuditHistoryByParams(_id: Int!, limit: Int, offset: Int): [AuditRunType] + getApplicationAuditHistoryCountByParams(_id: Int!): Int + getApplicationLatestAuditRunByParams(_id: Int!): AuditRunType + getAuditRunDetailsByParams(_id: Int!): AuditRunType } `; diff --git a/v6y-apps/bff/src/types/application/TriggerApplicationAnalysisInput.ts b/v6y-apps/bff/src/types/application/TriggerApplicationAnalysisInput.ts new file mode 100644 index 00000000..567530ab --- /dev/null +++ b/v6y-apps/bff/src/types/application/TriggerApplicationAnalysisInput.ts @@ -0,0 +1,14 @@ +const TriggerApplicationAnalysisInput = ` + input TriggerApplicationAnalysisInput { + """ Application ID to analyze """ + applicationId: Int! + + """ Git branch to analyze """ + branch: String + + """ Types of analysis to run (static, dynamic, devops) """ + analysisTypes: [String!]! + } +`; + +export default TriggerApplicationAnalysisInput; diff --git a/v6y-apps/bff/src/types/application/TriggerApplicationAnalysisOutput.ts b/v6y-apps/bff/src/types/application/TriggerApplicationAnalysisOutput.ts new file mode 100644 index 00000000..0972212c --- /dev/null +++ b/v6y-apps/bff/src/types/application/TriggerApplicationAnalysisOutput.ts @@ -0,0 +1,20 @@ +const TriggerApplicationAnalysisOutput = ` + type TriggerApplicationAnalysisOutput { + """ Whether the analysis was triggered successfully """ + success: Boolean! + + """ Message describing the result """ + message: String! + + """ Application ID """ + applicationId: Int! + + """ Git branch analyzed """ + branch: String + + """ The audit run that was created """ + auditRun: AuditRunType + } +`; + +export default TriggerApplicationAnalysisOutput; diff --git a/v6y-apps/bff/src/types/audit/AuditRunType.ts b/v6y-apps/bff/src/types/audit/AuditRunType.ts new file mode 100644 index 00000000..b591d8ff --- /dev/null +++ b/v6y-apps/bff/src/types/audit/AuditRunType.ts @@ -0,0 +1,38 @@ +const AuditRunType = ` + type AuditRunType { + """ Audit Run Unique id """ + _id: Int! + + """ Application ID that this audit run belongs to """ + appId: Int! + + """ Git branch that was analyzed """ + branch: String + + """ Status of the audit run (pending, in_progress, completed, failed) """ + runStatus: String! + + """ Types of analysis performed (static, dynamic, devops) """ + analysisTypes: [String!]! + + """ Audit reports generated in this run """ + audits: [AuditReportType!]! + + """ When the audit run was triggered """ + triggeredAt: String! + + """ When the audit run completed """ + completedAt: String + + """ Error message if the run failed """ + errorMessage: String + + """ When this record was created """ + createdAt: String! + + """ When this record was last updated """ + updatedAt: String! + } +`; + +export default AuditRunType; diff --git a/v6y-libs/core-logic/prisma/migrations/20260512000000_add_audit_run_history_tracking/migration.sql b/v6y-libs/core-logic/prisma/migrations/20260512000000_add_audit_run_history_tracking/migration.sql new file mode 100644 index 00000000..b7da6836 --- /dev/null +++ b/v6y-libs/core-logic/prisma/migrations/20260512000000_add_audit_run_history_tracking/migration.sql @@ -0,0 +1,36 @@ +-- CreateTable +CREATE TABLE "audit_runs" ( + "_id" SERIAL NOT NULL, + "app_id" INTEGER NOT NULL, + "branch" TEXT, + "run_status" TEXT NOT NULL DEFAULT 'pending', + "analysis_types" TEXT[] DEFAULT ARRAY[]::TEXT[], + "triggered_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "completed_at" TIMESTAMP(3), + "error_message" TEXT, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "audit_runs_pkey" PRIMARY KEY ("_id") +); + +-- Add column to audit_reports table +ALTER TABLE "audit_reports" ADD COLUMN "audit_run_id" INTEGER; + +-- AddForeignKey +ALTER TABLE "audit_runs" ADD CONSTRAINT "audit_runs_app_id_fkey" FOREIGN KEY ("app_id") REFERENCES "applications"("_id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "audit_reports" ADD CONSTRAINT "audit_reports_audit_run_id_fkey" FOREIGN KEY ("audit_run_id") REFERENCES "audit_runs"("_id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- CreateIndex +CREATE INDEX "audit_runs_app_id_idx" ON "audit_runs"("app_id"); + +-- CreateIndex +CREATE INDEX "audit_runs_triggered_at_idx" ON "audit_runs"("triggered_at"); + +-- CreateIndex +CREATE INDEX "audit_runs_run_status_idx" ON "audit_runs"("run_status"); + +-- CreateIndex +CREATE INDEX "audit_reports_audit_run_id_idx" ON "audit_reports"("audit_run_id"); diff --git a/v6y-libs/core-logic/prisma/schema.prisma b/v6y-libs/core-logic/prisma/schema.prisma index 54d84e25..bdca9eef 100644 --- a/v6y-libs/core-logic/prisma/schema.prisma +++ b/v6y-libs/core-logic/prisma/schema.prisma @@ -31,6 +31,7 @@ model Application { createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") audits Audit[] + auditRuns AuditRun[] dependencies Dependency[] evolutions Evolution[] keywords Keyword[] @@ -42,6 +43,8 @@ model Audit { id Int @id @default(autoincrement()) @map("_id") appId Int @map("app_id") application Application @relation(fields: [appId], references: [id], onDelete: Cascade) + auditRunId Int? @map("audit_run_id") + auditRun AuditRun? @relation(fields: [auditRunId], references: [id], onDelete: SetNull) dateStart DateTime? @map("date_start") dateEnd DateTime? @map("date_end") type String? @@ -58,10 +61,31 @@ model Audit { updatedAt DateTime @updatedAt @map("updated_at") @@index([appId]) + @@index([auditRunId]) @@index([appId, type, category]) @@map("audit_reports") } +model AuditRun { + id Int @id @default(autoincrement()) @map("_id") + appId Int @map("app_id") + application Application @relation(fields: [appId], references: [id], onDelete: Cascade) + branch String? + runStatus String @default("pending") @map("run_status") + analysisTypes String[] @default([]) @map("analysis_types") + audits Audit[] + triggeredAt DateTime @default(now()) @map("triggered_at") + completedAt DateTime? @map("completed_at") + errorMessage String? @map("error_message") + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @updatedAt @map("updated_at") + + @@index([appId]) + @@index([triggeredAt]) + @@index([runStatus]) + @@map("audit_runs") +} + model Dependency { id Int @id @default(autoincrement()) @map("_id") appId Int @map("app_id") diff --git a/v6y-libs/core-logic/src/database/AuditProvider.ts b/v6y-libs/core-logic/src/database/AuditProvider.ts index 1c10b879..9ffd023f 100644 --- a/v6y-libs/core-logic/src/database/AuditProvider.ts +++ b/v6y-libs/core-logic/src/database/AuditProvider.ts @@ -19,6 +19,7 @@ const createAudit = async (audit: AuditType) => { const created = await getPrismaClient().audit.create({ data: { appId: (audit.module?.appId ?? audit.appId)!, + auditRunId: audit.auditRunId ?? null, dateStart: audit.dateStart ?? null, dateEnd: audit.dateEnd ?? null, type: audit.type ?? null, @@ -56,6 +57,7 @@ const insertAuditList = async (auditList: AuditType[] | null) => { }); return { appId: (audit.module?.appId ?? audit.appId)!, + auditRunId: audit.auditRunId ?? null, dateStart: audit.dateStart ?? null, dateEnd: audit.dateEnd ?? null, type: audit.type ?? null, diff --git a/v6y-libs/core-logic/src/database/AuditRunProvider.ts b/v6y-libs/core-logic/src/database/AuditRunProvider.ts new file mode 100644 index 00000000..3729ec40 --- /dev/null +++ b/v6y-libs/core-logic/src/database/AuditRunProvider.ts @@ -0,0 +1,171 @@ +import AppLogger from '../core/AppLogger.ts'; +import { AuditRunType } from '../types/AuditRunType.ts'; +import { getPrismaClient } from './PrismaClient.ts'; + +const createAuditRun = async (auditRun: AuditRunType) => { + try { + AppLogger.info( + '[AuditRunProvider - createAuditRun] appId: ' + + auditRun?.appId + + ' analysisTypes: ' + + auditRun?.analysisTypes?.join(','), + ); + + if (!auditRun?.appId) { + AppLogger.error('[AuditRunProvider - createAuditRun] Missing appId'); + return null; + } + + const created = await getPrismaClient().auditRun.create({ + data: { + appId: auditRun.appId, + branch: auditRun.branch ?? null, + runStatus: auditRun.runStatus ?? 'pending', + analysisTypes: auditRun.analysisTypes ?? [], + errorMessage: auditRun.errorMessage ?? null, + }, + }); + + AppLogger.info('[AuditRunProvider - createAuditRun] created: ' + created.id); + return { ...created, _id: created.id }; + } catch (error) { + AppLogger.error('[AuditRunProvider - createAuditRun] error: ', error); + return null; + } +}; + +const updateAuditRunStatus = async ({ + auditRunId, + runStatus, + completedAt, + errorMessage, +}: { + auditRunId: number; + runStatus: string; + completedAt?: Date; + errorMessage?: string; +}) => { + try { + AppLogger.info( + '[AuditRunProvider - updateAuditRunStatus] auditRunId: ' + + auditRunId + + ' status: ' + + runStatus, + ); + + const updated = await getPrismaClient().auditRun.update({ + where: { id: auditRunId }, + data: { + runStatus, + completedAt: completedAt ?? null, + errorMessage: errorMessage ?? null, + }, + }); + + AppLogger.info('[AuditRunProvider - updateAuditRunStatus] updated: ' + updated.id); + return { ...updated, _id: updated.id }; + } catch (error) { + AppLogger.error('[AuditRunProvider - updateAuditRunStatus] error: ', error); + return null; + } +}; + +const getAuditRunById = async (auditRunId: number) => { + try { + const auditRun = await getPrismaClient().auditRun.findUnique({ + where: { id: auditRunId }, + include: { audits: true }, + }); + + AppLogger.info('[AuditRunProvider - getAuditRunById] found: ' + auditRunId); + return auditRun ? { ...auditRun, _id: auditRun.id } : null; + } catch (error) { + AppLogger.error('[AuditRunProvider - getAuditRunById] error: ', error); + return null; + } +}; + +const getAuditRunsByApplicationId = async (appId: number, limit?: number, offset?: number) => { + try { + AppLogger.info('[AuditRunProvider - getAuditRunsByApplicationId] appId: ' + appId); + + const skip = offset ?? 0; + const take = limit ?? 20; + + const auditRuns = await getPrismaClient().auditRun.findMany({ + where: { appId }, + orderBy: { triggeredAt: 'desc' }, + skip, + take, + include: { audits: true }, + }); + + AppLogger.info( + '[AuditRunProvider - getAuditRunsByApplicationId] count: ' + auditRuns?.length, + ); + return auditRuns.map((item) => ({ ...item, _id: item.id })); + } catch (error) { + AppLogger.error('[AuditRunProvider - getAuditRunsByApplicationId] error: ', error); + return []; + } +}; + +const getLatestAuditRun = async (appId: number) => { + try { + AppLogger.info('[AuditRunProvider - getLatestAuditRun] appId: ' + appId); + + const auditRun = await getPrismaClient().auditRun.findFirst({ + where: { appId }, + orderBy: { triggeredAt: 'desc' }, + include: { audits: true }, + }); + + AppLogger.info('[AuditRunProvider - getLatestAuditRun] found: ' + auditRun?.id); + return auditRun ? { ...auditRun, _id: auditRun.id } : null; + } catch (error) { + AppLogger.error('[AuditRunProvider - getLatestAuditRun] error: ', error); + return null; + } +}; + +const getAuditRunsCountByApplicationId = async (appId: number) => { + try { + const count = await getPrismaClient().auditRun.count({ + where: { appId }, + }); + + AppLogger.info('[AuditRunProvider - getAuditRunsCountByApplicationId] count: ' + count); + return count; + } catch (error) { + AppLogger.error('[AuditRunProvider - getAuditRunsCountByApplicationId] error: ', error); + return 0; + } +}; + +const deleteAuditRun = async (auditRunId: number) => { + try { + AppLogger.info('[AuditRunProvider - deleteAuditRun] auditRunId: ' + auditRunId); + + await getPrismaClient().auditRun.delete({ + where: { id: auditRunId }, + }); + + AppLogger.info('[AuditRunProvider - deleteAuditRun] deleted: ' + auditRunId); + return { _id: auditRunId }; + } catch (error) { + AppLogger.error('[AuditRunProvider - deleteAuditRun] error: ', error); + return null; + } +}; + +const AuditRunProvider = { + createAuditRun, + updateAuditRunStatus, + getAuditRunById, + getAuditRunsByApplicationId, + getLatestAuditRun, + getAuditRunsCountByApplicationId, + deleteAuditRun, +}; + +export default AuditRunProvider; diff --git a/v6y-libs/core-logic/src/index.ts b/v6y-libs/core-logic/src/index.ts index 9746ddf4..e36dda18 100644 --- a/v6y-libs/core-logic/src/index.ts +++ b/v6y-libs/core-logic/src/index.ts @@ -12,6 +12,7 @@ import AccountProvider from './database/AccountProvider.ts'; import ApplicationProvider from './database/ApplicationProvider.ts'; import AuditHelpProvider from './database/AuditHelpProvider.ts'; import AuditProvider from './database/AuditProvider.ts'; +import AuditRunProvider from './database/AuditRunProvider.ts'; import DataBaseManager from './database/DataBaseManager.ts'; import DependencyProvider from './database/DependencyProvider.ts'; import DependencyStatusHelpProvider from './database/DependencyStatusHelpProvider.ts'; @@ -47,6 +48,7 @@ export { KeywordProvider, DataBaseManager, AuditProvider, + AuditRunProvider, AuditHelpProvider, FaqProvider, AccountProvider, diff --git a/v6y-libs/core-logic/src/types/AuditRunType.ts b/v6y-libs/core-logic/src/types/AuditRunType.ts new file mode 100644 index 00000000..36156354 --- /dev/null +++ b/v6y-libs/core-logic/src/types/AuditRunType.ts @@ -0,0 +1,12 @@ +export interface AuditRunType { + _id?: number; + appId: number; + branch?: string; + runStatus?: string; + analysisTypes?: string[]; + triggeredAt?: Date; + completedAt?: Date | null; + errorMessage?: string | null; + createdAt?: Date; + updatedAt?: Date; +} diff --git a/v6y-libs/core-logic/src/types/AuditType.ts b/v6y-libs/core-logic/src/types/AuditType.ts index 2ac3dd4e..2fa1aaca 100644 --- a/v6y-libs/core-logic/src/types/AuditType.ts +++ b/v6y-libs/core-logic/src/types/AuditType.ts @@ -4,6 +4,7 @@ import { ModuleType } from './ModuleType.ts'; export interface AuditType { _id?: number; appId?: number; + auditRunId?: number | null; dateStart?: Date; dateEnd?: Date; type?: string; diff --git a/v6y-libs/core-logic/src/types/index.ts b/v6y-libs/core-logic/src/types/index.ts index 43c558ec..3c1e34fd 100644 --- a/v6y-libs/core-logic/src/types/index.ts +++ b/v6y-libs/core-logic/src/types/index.ts @@ -2,6 +2,7 @@ export * from './AccountType.ts'; export * from './ApplicationType.ts'; export * from './AuditHelpType.ts'; export * from './AuditType.ts'; +export * from './AuditRunType.ts'; export * from './AuditParserType.ts'; export * from './DependencyType.ts'; export * from './DependencyStatusHelpType.ts'; From 9d436dab81cadb558075de8da2cbd2d0f02b651f Mon Sep 17 00:00:00 2001 From: "theo.gillet" Date: Wed, 13 May 2026 09:31:47 +0200 Subject: [PATCH 2/7] [WIP]: added auditRun Id when triggering audit reports --- v6y-apps/bfb-devops-auditor/package.json | 2 +- .../src/auditors/DevOpsAuditorManager.ts | 10 +- .../dora-metrics/DoraMetricsAuditor.ts | 12 +- .../src/auditors/types/AuditCommonsType.ts | 1 + .../auditors/types/DoraMetricsAuditType.ts | 1 + .../src/routes/DevOpsAuditorRouter.ts | 4 +- .../src/workers/DevOpsAnalysisWorker.ts | 4 +- v6y-apps/bfb-dynamic-auditor/package.json | 2 +- .../src/auditors/DynamicAuditorManager.ts | 3 - v6y-apps/bfb-main-analyzer/package.json | 2 +- v6y-apps/bfb-main-analyzer/src/app.ts | 128 +++++++++++++++- .../src/config/ServerConfig.ts | 71 ++++++++- .../src/managers/ApplicationManager.ts | 68 ++++++-- .../src/managers/BranchManager.ts | 9 ++ .../src/workers/ApplicationWorker.ts | 25 +-- .../src/workers/DataUpdateScheduler.ts | 15 +- v6y-apps/bfb-static-auditor/package.json | 2 +- .../src/auditors/StaticAuditorManager.ts | 5 +- .../bundle-analyzer/BundleAnalyzeAuditor.ts | 15 +- .../code-complexity/CodeComplexityAuditor.ts | 15 +- .../code-coupling/CodeCouplingAuditor.ts | 15 +- .../CodeDuplicationAuditor.ts | 15 +- .../code-modularity/CodeModularityAuditor.ts | 15 +- .../code-security/CodeSecurityAuditor.ts | 15 +- .../ecological-impact/CreedengoAuditor.ts | 15 +- v6y-apps/bff/package.json | 2 +- v6y-apps/bff/src/config/ServerConfig.ts | 7 + .../application/ApplicationMutations.ts | 145 +++--------------- .../core-logic/src/config/ServerConfig.ts | 10 +- 29 files changed, 394 insertions(+), 239 deletions(-) diff --git a/v6y-apps/bfb-devops-auditor/package.json b/v6y-apps/bfb-devops-auditor/package.json index 11dd461a..0f5ebe0a 100644 --- a/v6y-apps/bfb-devops-auditor/package.json +++ b/v6y-apps/bfb-devops-auditor/package.json @@ -15,7 +15,7 @@ "scripts": { "build:tsc": "tsc --project ./tsconfig.json", "check:tsc": "tsc --noEmit --project ./tsconfig.json", - "start:dev": "nodemon --exec node --env-file=./.env --loader ts-node/esm src/index.ts -- --dev", + "start:dev": "NODE_ENV=development nodemon --exec node --env-file=./.env --loader ts-node/esm src/index.ts -- --dev", "start": "nodemon --exec node --env-file=./.env --loader ts-node/esm src/index.ts --", "code:analyze": "es6-plato -r -d target/report src", "lint": "eslint --config ./eslint.config.mjs --cache-location ./.eslintcache \"./**/*.ts\" --cache", diff --git a/v6y-apps/bfb-devops-auditor/src/auditors/DevOpsAuditorManager.ts b/v6y-apps/bfb-devops-auditor/src/auditors/DevOpsAuditorManager.ts index b2f144cf..d0fc4680 100644 --- a/v6y-apps/bfb-devops-auditor/src/auditors/DevOpsAuditorManager.ts +++ b/v6y-apps/bfb-devops-auditor/src/auditors/DevOpsAuditorManager.ts @@ -4,25 +4,25 @@ */ import { AppLogger, WorkerHelper } from '@v6y/core-logic'; -import ServerConfig from '../commons/ServerConfig.ts'; import { AuditCommonsType } from './types/AuditCommonsType.ts'; const { forkWorker } = WorkerHelper; -const { currentConfig } = ServerConfig; /** * Starts the DevOps audit process for a given application * @param applicationId + * @param auditRunId */ -const startDevOpsAudit = async ({ applicationId }: AuditCommonsType) => { +const startDevOpsAudit = async ({ applicationId, auditRunId }: AuditCommonsType) => { try { AppLogger.info('[DevOpsAuditorManager - startDevOpsAudit] applicationId: ', applicationId); + AppLogger.info('[DevOpsAuditorManager - startDevOpsAudit] auditRunId: ', auditRunId); // Start audits in a worker thread to prevent blocking the main thread const workerConfig = { - ...currentConfig, applicationId, - } as WorkerOptions; + auditRunId, + }; await forkWorker('./src/workers/DevOpsAnalysisWorker.ts', workerConfig); diff --git a/v6y-apps/bfb-devops-auditor/src/auditors/dora-metrics/DoraMetricsAuditor.ts b/v6y-apps/bfb-devops-auditor/src/auditors/dora-metrics/DoraMetricsAuditor.ts index 0b6e0a55..a064405f 100644 --- a/v6y-apps/bfb-devops-auditor/src/auditors/dora-metrics/DoraMetricsAuditor.ts +++ b/v6y-apps/bfb-devops-auditor/src/auditors/dora-metrics/DoraMetricsAuditor.ts @@ -19,11 +19,12 @@ const { analyseDoraMetrics } = DoraMetricsUtils; * Starts the Dora Metrics auditor analysis. * @param auditConfig */ -const startAuditorAnalysis = async ({ applicationId }: DoraMetricsAuditConfigType) => { +const startAuditorAnalysis = async ({ applicationId, auditRunId }: DoraMetricsAuditConfigType) => { try { AppLogger.info( `[DoraMetricsAuditor - startAuditorAnalysis] applicationId: ${applicationId}`, ); + AppLogger.info(`[DoraMetricsAuditor - startAuditorAnalysis] auditRunId: ${auditRunId}`); if (applicationId === undefined) { return false; @@ -69,6 +70,15 @@ const startAuditorAnalysis = async ({ applicationId }: DoraMetricsAuditConfigTyp auditReports.push(...reports); } + // Add auditRunId to each report + if (auditRunId) { + const auditRunIdNum = + typeof auditRunId === 'string' ? parseInt(auditRunId, 10) : auditRunId; + auditReports.forEach((audit) => { + audit.auditRunId = auditRunIdNum; + }); + } + await AuditProvider.insertAuditList(auditReports); AppLogger.info( diff --git a/v6y-apps/bfb-devops-auditor/src/auditors/types/AuditCommonsType.ts b/v6y-apps/bfb-devops-auditor/src/auditors/types/AuditCommonsType.ts index ba3bd07b..a55d3196 100644 --- a/v6y-apps/bfb-devops-auditor/src/auditors/types/AuditCommonsType.ts +++ b/v6y-apps/bfb-devops-auditor/src/auditors/types/AuditCommonsType.ts @@ -3,4 +3,5 @@ import { ApplicationType } from '@v6y/core-logic'; export interface AuditCommonsType { applicationId?: number; application?: ApplicationType; + auditRunId?: string; } diff --git a/v6y-apps/bfb-devops-auditor/src/auditors/types/DoraMetricsAuditType.ts b/v6y-apps/bfb-devops-auditor/src/auditors/types/DoraMetricsAuditType.ts index 98c8d5a6..b5f75649 100644 --- a/v6y-apps/bfb-devops-auditor/src/auditors/types/DoraMetricsAuditType.ts +++ b/v6y-apps/bfb-devops-auditor/src/auditors/types/DoraMetricsAuditType.ts @@ -3,6 +3,7 @@ import { MonitoringEventType, RepositoryType } from '@v6y/core-logic'; export interface DoraMetricsAuditConfigType { applicationId?: number; + auditRunId?: string; } export interface startDoraMetricsAnalysisParamsType { diff --git a/v6y-apps/bfb-devops-auditor/src/routes/DevOpsAuditorRouter.ts b/v6y-apps/bfb-devops-auditor/src/routes/DevOpsAuditorRouter.ts index c9c39d35..4e37fd40 100644 --- a/v6y-apps/bfb-devops-auditor/src/routes/DevOpsAuditorRouter.ts +++ b/v6y-apps/bfb-devops-auditor/src/routes/DevOpsAuditorRouter.ts @@ -12,10 +12,11 @@ DevOpsAuditorRouter.post('/start-devops-auditor.json', async (request, response) try { AppLogger.debug('[DevOpsAuditorRouter] Entering service: [start-devops-auditor]'); - const { applicationId } = request.body || {}; + const { applicationId, auditRunId } = request.body || {}; // ********************************************** Input Validation *********************************************** AppLogger.info(`[DevOpsAuditorRouter] applicationId: ${applicationId}`); + AppLogger.info(`[DevOpsAuditorRouter] auditRunId: ${auditRunId}`); if (!applicationId) { AppLogger.error( @@ -32,6 +33,7 @@ DevOpsAuditorRouter.post('/start-devops-auditor.json', async (request, response) // ********************************************** Start Audits *********************************************** const auditsStartedSuccessfully = await DevOpsAuditorManager.startDevOpsAudit({ applicationId, + auditRunId, }); // Wait for audits to potentially complete // ********************************************** Server Response *********************************************** diff --git a/v6y-apps/bfb-devops-auditor/src/workers/DevOpsAnalysisWorker.ts b/v6y-apps/bfb-devops-auditor/src/workers/DevOpsAnalysisWorker.ts index 8ebebe40..6e308fb5 100644 --- a/v6y-apps/bfb-devops-auditor/src/workers/DevOpsAnalysisWorker.ts +++ b/v6y-apps/bfb-devops-auditor/src/workers/DevOpsAnalysisWorker.ts @@ -7,8 +7,9 @@ import DoraMetricsAuditor from '../auditors/dora-metrics/DoraMetricsAuditor.ts'; AppLogger.info('******************** Starting background Audit **************************'); try { - const { applicationId } = workerData || {}; + const { applicationId, auditRunId } = workerData || {}; AppLogger.info(`[DoraMetricsAnalysisWorker] applicationId: ${applicationId}`); + AppLogger.info(`[DoraMetricsAnalysisWorker] auditRunId: ${auditRunId}`); // *********************************************** Database Configuration and Connection *********************************************** await DataBaseManager.connect(); @@ -18,6 +19,7 @@ try { const result = await DoraMetricsAuditor.startAuditorAnalysis({ applicationId, + auditRunId, }); PerformancesUtils.endMeasure('DoraMetricsAnalysisWorker-startAuditorAnalysis'); diff --git a/v6y-apps/bfb-dynamic-auditor/package.json b/v6y-apps/bfb-dynamic-auditor/package.json index d6887057..f920133a 100644 --- a/v6y-apps/bfb-dynamic-auditor/package.json +++ b/v6y-apps/bfb-dynamic-auditor/package.json @@ -15,7 +15,7 @@ "scripts": { "build:tsc": "tsc --project ./tsconfig.json", "check:tsc": "tsc --noEmit --project ./tsconfig.json", - "start:dev": "nodemon --exec node --env-file=./.env --loader ts-node/esm src/index.ts -- --dev", + "start:dev": "NODE_ENV=development nodemon --exec node --env-file=./.env --loader ts-node/esm src/index.ts -- --dev", "start": "nodemon --exec node --env-file=./.env --loader ts-node/esm src/index.ts --", "code:analyze": "es6-plato -r -d target/report src", "lint": "eslint --config ./eslint.config.mjs --cache-location ./.eslintcache \"./**/*.ts\" --cache", diff --git a/v6y-apps/bfb-dynamic-auditor/src/auditors/DynamicAuditorManager.ts b/v6y-apps/bfb-dynamic-auditor/src/auditors/DynamicAuditorManager.ts index 7e7f8b83..c13f76b4 100644 --- a/v6y-apps/bfb-dynamic-auditor/src/auditors/DynamicAuditorManager.ts +++ b/v6y-apps/bfb-dynamic-auditor/src/auditors/DynamicAuditorManager.ts @@ -1,10 +1,8 @@ import { AppLogger, WorkerHelper } from '@v6y/core-logic'; -import ServerConfig from '../commons/ServerConfig.ts'; import { AuditCommonsType } from './types/AuditCommonsType.ts'; const { forkWorker } = WorkerHelper; -const { currentConfig } = ServerConfig; const startDynamicAudit = async ({ applicationId, auditRunId }: AuditCommonsType) => { try { @@ -16,7 +14,6 @@ const startDynamicAudit = async ({ applicationId, auditRunId }: AuditCommonsType // Start audits in a worker thread to prevent blocking the main thread const workerConfig = { - ...currentConfig, applicationId, auditRunId, }; diff --git a/v6y-apps/bfb-main-analyzer/package.json b/v6y-apps/bfb-main-analyzer/package.json index 71054e3f..c590af7e 100644 --- a/v6y-apps/bfb-main-analyzer/package.json +++ b/v6y-apps/bfb-main-analyzer/package.json @@ -15,7 +15,7 @@ "scripts": { "build:tsc": "tsc --project ./tsconfig.json", "check:tsc": "tsc --noEmit --project ./tsconfig.json", - "start:dev": "nodemon --exec node --env-file=./.env --loader ts-node/esm src/index.ts -- --dev", + "start:dev": "NODE_ENV=development nodemon --exec node --env-file=./.env --loader ts-node/esm src/index.ts -- --dev", "start": "nodemon --exec node --env-file=./.env --loader ts-node/esm src/index.ts --", "lint": "eslint --config ./eslint.config.mjs --cache-location ./.eslintcache \"./**/*.ts\" --cache", "lint:fix": "eslint --config ./eslint.config.mjs --cache-location ./.eslintcache \"./**/*.ts\" --cache --fix", diff --git a/v6y-apps/bfb-main-analyzer/src/app.ts b/v6y-apps/bfb-main-analyzer/src/app.ts index 7cbef461..94a903ae 100644 --- a/v6y-apps/bfb-main-analyzer/src/app.ts +++ b/v6y-apps/bfb-main-analyzer/src/app.ts @@ -4,9 +4,10 @@ import Cors from 'cors'; import Express, { Express as ExpressApp } from 'express'; import ExpressStatusMonitor from 'express-status-monitor'; -import { AppLogger, CorsOptions } from '@v6y/core-logic'; +import { AppLogger, ApplicationProvider, AuditRunProvider, CorsOptions } from '@v6y/core-logic'; import ServerConfig from './config/ServerConfig.ts'; +import { buildDynamicReports, buildStaticReports } from './managers/AuditManager.ts'; /** * Creates and configures the Express application @@ -46,6 +47,131 @@ export function createApp(): ExpressApp { }), ); + // *********************************************** Trigger Manual Audit Endpoint *********************************************** + + app.post('/trigger-audit', async (request, response) => { + try { + const { applicationId, branch, analysisTypes } = request.body || {}; + + AppLogger.info( + `[MainAnalyzerApp - triggerAudit] applicationId: ${applicationId}, branch: ${branch}, analysisTypes: ${analysisTypes?.join(',')}`, + ); + + if (!applicationId) { + response.status(400).json({ + success: false, + message: 'applicationId is required', + }); + return; + } + + // Get application details + const application = await ApplicationProvider.getApplicationDetailsInfoByParams({ + _id: applicationId, + }); + + if (!application?._id) { + response.status(404).json({ + success: false, + message: 'Application not found', + }); + return; + } + + // Create AuditRun record + const auditRun = await AuditRunProvider.createAuditRun({ + appId: applicationId, + branch: branch || null, + runStatus: 'pending', + analysisTypes: analysisTypes || ['static', 'dynamic', 'devops'], + }); + + if (!auditRun?._id) { + response.status(500).json({ + success: false, + message: 'Failed to create audit run', + }); + return; + } + + const auditRunId = String(auditRun._id); + + // Update status to in_progress + await AuditRunProvider.updateAuditRunStatus({ + auditRunId, + runStatus: 'in_progress', + }); + + // Trigger audits asynchronously + (async () => { + try { + const branchesToAnalyze = branch + ? [{ name: branch }] + : application?.repo?.allBranches?.map((b: string) => ({ name: b })) || []; + + // Trigger static auditor if included + if (analysisTypes?.includes('static') || !analysisTypes) { + AppLogger.info( + `[MainAnalyzerApp - triggerAudit] Triggering static auditor for app ${applicationId}`, + ); + await buildStaticReports({ + application, + branches: branchesToAnalyze, + auditRunId, + }); + } + + // Trigger dynamic auditor if included + if (analysisTypes?.includes('dynamic') || !analysisTypes) { + AppLogger.info( + `[MainAnalyzerApp - triggerAudit] Triggering dynamic auditor for app ${applicationId}`, + ); + await buildDynamicReports({ + application, + auditRunId, + }); + } + + // Update AuditRun status to completed + await AuditRunProvider.updateAuditRunStatus({ + auditRunId, + runStatus: 'completed', + completedAt: new Date(), + }); + + AppLogger.info( + `[MainAnalyzerApp - triggerAudit] AuditRun completed: ${auditRunId}`, + ); + } catch (asyncError) { + AppLogger.error( + `[MainAnalyzerApp - triggerAudit] Async audit error: ${asyncError}`, + ); + await AuditRunProvider.updateAuditRunStatus({ + auditRunId, + runStatus: 'error', + errorMessage: String(asyncError), + }).catch((err) => + AppLogger.warn( + `[MainAnalyzerApp - triggerAudit] Failed to update error status: ${err}`, + ), + ); + } + })(); + + response.status(200).json({ + success: true, + message: 'Audit triggered successfully', + auditRun, + }); + } catch (error) { + AppLogger.error('[MainAnalyzerApp - triggerAudit] error: ', error); + response.status(500).json({ + success: false, + message: 'Failed to trigger audit', + }); + } + }); + // *********************************************** Handle Endpoints *********************************************** // default response (unknown routes) diff --git a/v6y-apps/bfb-main-analyzer/src/config/ServerConfig.ts b/v6y-apps/bfb-main-analyzer/src/config/ServerConfig.ts index 92c89a8f..48f18e59 100644 --- a/v6y-apps/bfb-main-analyzer/src/config/ServerConfig.ts +++ b/v6y-apps/bfb-main-analyzer/src/config/ServerConfig.ts @@ -1,18 +1,75 @@ -import { ServerEnvConfigType, getServerConfig, normalizeBasePath } from '@v6y/core-logic'; +import { + AppLogger, + ServerEnvConfigType, + getServerConfig, + normalizeBasePath, +} from '@v6y/core-logic'; const V6Y_API_BASE_PATH = normalizeBasePath(process.env.V6Y_MAIN_API_PATH); const V6Y_HEALTH_CHECK_PATH = `${V6Y_API_BASE_PATH}health-checks`; const V6Y_MONITORING_PATH = `${V6Y_API_BASE_PATH}monitoring`; +// Construct full URLs for auditor services +const buildAuditorUrl = ( + apiPath: string | undefined, + port: string | undefined, + endpoint: string, +): string => { + if (!apiPath || !port) { + AppLogger.warn(`[buildAuditorUrl] Missing apiPath: ${apiPath} or port: ${port}`); + return ''; + } + const normalizedPath = normalizeBasePath(apiPath); + const normalizedEndpoint = endpoint.startsWith('/') ? endpoint : `/${endpoint}`; + const fullUrl = `http://localhost:${port}${normalizedPath}${normalizedEndpoint}`; + AppLogger.debug(`[buildAuditorUrl] apiPath: ${apiPath}, port: ${port}, endpoint: ${endpoint}`); + AppLogger.debug( + `[buildAuditorUrl] normalizedPath: ${normalizedPath}, normalizedEndpoint: ${normalizedEndpoint}`, + ); + AppLogger.debug(`[buildAuditorUrl] Result: ${fullUrl}`); + return fullUrl; +}; + +const STATIC_AUDITOR_ENDPOINT = buildAuditorUrl( + process.env.V6Y_STATIC_ANALYZER_API_PATH, + process.env.V6Y_STATIC_ANALYZER_API_PORT, + 'auditor/start-static-auditor.json', +); + +const DYNAMIC_AUDITOR_ENDPOINT = buildAuditorUrl( + process.env.V6Y_DYNAMIC_ANALYZER_API_PATH, + process.env.V6Y_DYNAMIC_ANALYZER_API_PORT, + 'auditor/start-dynamic-auditor.json', +); + +const DEVOPS_AUDITOR_ENDPOINT = buildAuditorUrl( + process.env.V6Y_DEVOPS_API_PATH, + process.env.V6Y_DEVOPS_API_PORT, + 'auditor/start-devops-auditor.json', +); + +// Log environment variables for debugging +AppLogger.debug('[ServerConfig] Environment variables:'); +AppLogger.debug(` V6Y_STATIC_ANALYZER_API_PATH: ${process.env.V6Y_STATIC_ANALYZER_API_PATH}`); +AppLogger.debug(` V6Y_STATIC_ANALYZER_API_PORT: ${process.env.V6Y_STATIC_ANALYZER_API_PORT}`); +AppLogger.debug(` V6Y_DYNAMIC_ANALYZER_API_PATH: ${process.env.V6Y_DYNAMIC_ANALYZER_API_PATH}`); +AppLogger.debug(` V6Y_DYNAMIC_ANALYZER_API_PORT: ${process.env.V6Y_DYNAMIC_ANALYZER_API_PORT}`); +AppLogger.debug(` V6Y_DEVOPS_API_PATH: ${process.env.V6Y_DEVOPS_API_PATH}`); +AppLogger.debug(` V6Y_DEVOPS_API_PORT: ${process.env.V6Y_DEVOPS_API_PORT}`); +AppLogger.debug('[ServerConfig] Constructed URLs:'); +AppLogger.debug(` STATIC_AUDITOR_ENDPOINT: ${STATIC_AUDITOR_ENDPOINT}`); +AppLogger.debug(` DYNAMIC_AUDITOR_ENDPOINT: ${DYNAMIC_AUDITOR_ENDPOINT}`); +AppLogger.debug(` DEVOPS_AUDITOR_ENDPOINT: ${DEVOPS_AUDITOR_ENDPOINT}`); + const SERVER_ENV_CONFIGURATION = { production: { ssl: false, port: parseInt(process.env.V6Y_MAIN_API_PORT || '4002', 10), hostname: 'localhost', apiPath: process.env.V6Y_MAIN_API_PATH, - staticAuditorApiPath: process.env.V6Y_STATIC_ANALYZER_API_PATH, - dynamicAuditorApiPath: process.env.V6Y_DYNAMIC_ANALYZER_API_PATH, - devopsAuditorApiPath: process.env.V6Y_DEVOPS_ANALYSER_API_PATH, + staticAuditorApiPath: STATIC_AUDITOR_ENDPOINT, + dynamicAuditorApiPath: DYNAMIC_AUDITOR_ENDPOINT, + devopsAuditorApiPath: DEVOPS_AUDITOR_ENDPOINT, healthCheckPath: V6Y_HEALTH_CHECK_PATH, monitoringPath: V6Y_MONITORING_PATH, serverTimeout: 900000, // milliseconds @@ -22,9 +79,9 @@ const SERVER_ENV_CONFIGURATION = { port: parseInt(process.env.V6Y_MAIN_API_PORT || '4002', 10), hostname: 'localhost', apiPath: process.env.V6Y_MAIN_API_PATH, - staticAuditorApiPath: process.env.V6Y_STATIC_ANALYZER_API_PATH, - dynamicAuditorApiPath: process.env.V6Y_DYNAMIC_ANALYZER_API_PATH, - devopsAuditorApiPath: process.env.V6Y_DEVOPS_ANALYSER_API_PATH, + staticAuditorApiPath: STATIC_AUDITOR_ENDPOINT, + dynamicAuditorApiPath: DYNAMIC_AUDITOR_ENDPOINT, + devopsAuditorApiPath: DEVOPS_AUDITOR_ENDPOINT, healthCheckPath: V6Y_HEALTH_CHECK_PATH, monitoringPath: V6Y_MONITORING_PATH, serverTimeout: 900000, // milliseconds diff --git a/v6y-apps/bfb-main-analyzer/src/managers/ApplicationManager.ts b/v6y-apps/bfb-main-analyzer/src/managers/ApplicationManager.ts index d2957d56..2d298f90 100644 --- a/v6y-apps/bfb-main-analyzer/src/managers/ApplicationManager.ts +++ b/v6y-apps/bfb-main-analyzer/src/managers/ApplicationManager.ts @@ -4,6 +4,7 @@ import { ApplicationType, AuditRunProvider, RepositoryApi, + RepositoryBranchType, } from '@v6y/core-logic'; import { buildDynamicReports, buildStaticReports } from './AuditManager.ts'; @@ -32,27 +33,49 @@ const buildApplicationReports = async (application: ApplicationType) => { const { organization, gitUrl } = application.repo; const gitRepositoryName = gitUrl?.split('/')?.pop()?.replace('.git', ''); - const repositoryDetails = await getRepositoryDetails({ - organization, - gitRepositoryName, - }); + let repositoryDetails; + let repositoryBranches: RepositoryBranchType[] = []; + + try { + repositoryDetails = await getRepositoryDetails({ + organization, + gitRepositoryName, + }); + } catch (repoError) { + AppLogger.warn( + `[ApplicationManager - buildApplicationReports] Failed to fetch repository details for ${application.name}: ${repoError}. Will use default branches.`, + ); + } if ( !repositoryDetails?.id || repositoryDetails?.archived || repositoryDetails?.empty_repo ) { - return false; + AppLogger.warn( + `[ApplicationManager - buildApplicationReports] Repository check failed for ${application.name}, using default branches only.`, + ); + } else { + const { _links: repositoryLinks } = repositoryDetails; + + try { + repositoryBranches = await getRepositoryBranches({ + repoBranchesUrl: repositoryLinks?.repo_branches, + }); + } catch (branchError) { + AppLogger.warn( + `[ApplicationManager - buildApplicationReports] Failed to fetch branches for ${application.name}: ${branchError}`, + ); + } } - const { _links: repositoryLinks } = repositoryDetails; - - const repositoryBranches = await getRepositoryBranches({ - repoBranchesUrl: repositoryLinks?.repo_branches, - }); - + // If no branches found, attempt to use default branch names if (!repositoryBranches?.length) { - return false; + AppLogger.warn( + `[ApplicationManager - buildApplicationReports] No branches found for ${application.name}, attempting default branches (main/master)`, + ); + // Create default branch objects for main and master + repositoryBranches = [{ name: 'main' }, { name: 'master' }]; } await ApplicationProvider.editApplication({ @@ -141,26 +164,41 @@ const buildApplicationReports = async (application: ApplicationType) => { */ const buildApplicationList = async () => { try { + AppLogger.info( + '[ApplicationManager - buildApplicationList] Fetching applications from database...', + ); const applications = await ApplicationProvider.getApplicationListByPageAndParams( {}, { role: 'ADMIN' }, ); + const appCount = applications?.length || 0; AppLogger.info( - '[ApplicationManager - buildApplicationList] applications: ', - applications?.length, + `[ApplicationManager - buildApplicationList] Found ${appCount} application(s) to process`, ); if (!applications?.length) { + AppLogger.warn( + '[ApplicationManager - buildApplicationList] No applications found in database', + ); return false; } + AppLogger.info( + `[ApplicationManager - buildApplicationList] Processing ${appCount} applications...`, + ); for (const application of applications) { + AppLogger.info( + `[ApplicationManager - buildApplicationList] Processing application: ${application.name}`, + ); await buildApplicationReports(application); } + AppLogger.info( + '[ApplicationManager - buildApplicationList] ✅ All applications processed successfully', + ); return true; } catch (error) { - AppLogger.info('[ApplicationManager - buildApplicationList] error: ', error); + AppLogger.error('[ApplicationManager - buildApplicationList] ❌ Error: ', error); return false; } }; diff --git a/v6y-apps/bfb-main-analyzer/src/managers/BranchManager.ts b/v6y-apps/bfb-main-analyzer/src/managers/BranchManager.ts index 5ddc8cae..d38d389d 100644 --- a/v6y-apps/bfb-main-analyzer/src/managers/BranchManager.ts +++ b/v6y-apps/bfb-main-analyzer/src/managers/BranchManager.ts @@ -104,6 +104,15 @@ export const buildApplicationFrontendByBranch = async ({ ); try { + AppLogger.debug( + '[ApplicationManager - buildApplicationFrontendByBranch] URL type: ', + typeof staticAuditorApiPath, + ); + AppLogger.debug( + '[ApplicationManager - buildApplicationFrontendByBranch] URL length: ', + typeof staticAuditorApiPath === 'string' ? staticAuditorApiPath.length : 'N/A', + ); + const response = await fetch(staticAuditorApiPath as string, { method: 'POST', headers: { 'Content-Type': 'application/json' }, diff --git a/v6y-apps/bfb-main-analyzer/src/workers/ApplicationWorker.ts b/v6y-apps/bfb-main-analyzer/src/workers/ApplicationWorker.ts index 1327c164..4e0de0c8 100644 --- a/v6y-apps/bfb-main-analyzer/src/workers/ApplicationWorker.ts +++ b/v6y-apps/bfb-main-analyzer/src/workers/ApplicationWorker.ts @@ -1,29 +1,34 @@ import { parentPort } from 'worker_threads'; -import { AppLogger, AuditProvider, DataBaseManager, DependencyProvider } from '@v6y/core-logic'; +import { AppLogger, DataBaseManager, DependencyProvider } from '@v6y/core-logic'; import ApplicationManager from '../managers/ApplicationManager.ts'; const { buildApplicationList } = ApplicationManager; -AppLogger.info('******************** Starting background APP updates **************************'); +AppLogger.info('🔄 [ApplicationWorker] Starting background APP updates'); +AppLogger.info(`⏰ [ApplicationWorker] Timestamp: ${new Date().toISOString()}`); try { // *********************************************** Database Configuration and Connection *********************************************** + AppLogger.info('[ApplicationWorker] Connecting to database...'); await DataBaseManager.connect(); + AppLogger.info('[ApplicationWorker] ✅ Database connected'); - // Clear dynamic data in preparation for updates - await AuditProvider.deleteAuditList(); + // *********************************************** Update APP List *********************************************** + // Note: We don't delete audits anymore because they should be kept for historical tracking + // with timestamps (createdAt field). Only dependencies are cleared before fresh analysis. + AppLogger.info('[ApplicationWorker] Clearing old dependencies...'); await DependencyProvider.deleteDependencyList(); + AppLogger.info('[ApplicationWorker] ✅ Dependencies cleared'); - // *********************************************** Update APP List *********************************************** + AppLogger.info('[ApplicationWorker] Starting application analysis...'); await buildApplicationList(); + AppLogger.info('[ApplicationWorker] ✅ Application analysis completed'); } catch (error) { - AppLogger.info(`[createOrUpdateAppList] error : ${error}`); + AppLogger.error(`[ApplicationWorker] ❌ Error: ${error}`); } -AppLogger.info('******************** APP update completed successfully ********************'); +AppLogger.info('✅ [ApplicationWorker] APP update completed successfully'); -parentPort?.postMessage( - '******************** APP update completed successfully ********************', -); +parentPort?.postMessage('✅ [ApplicationWorker] APP update completed successfully'); diff --git a/v6y-apps/bfb-main-analyzer/src/workers/DataUpdateScheduler.ts b/v6y-apps/bfb-main-analyzer/src/workers/DataUpdateScheduler.ts index f6324301..23379c85 100644 --- a/v6y-apps/bfb-main-analyzer/src/workers/DataUpdateScheduler.ts +++ b/v6y-apps/bfb-main-analyzer/src/workers/DataUpdateScheduler.ts @@ -27,22 +27,13 @@ const startUpdateWorkers = () => { * Database updates are performed by default at startup, then every midnight. */ const start = () => { - // Check every second to make sure the main thread is still responsive - setInterval(() => { - AppLogger.info( - '******************** Checking that the main thread is not blocked **************************', - ); - }, 1000); - - // Initial update - AppLogger.info('******************** Starting initial update **************************'); setTimeout(() => { + AppLogger.info('[DataUpdateScheduler] Starting initial data update...'); startUpdateWorkers(); - }, 2000); // Delay the initial update by 2 seconds + }, 2000); - // Schedule periodic updates (every midnight) const job = CronJob.schedule('00 00 00 * * *', () => { - AppLogger.info('******************** Starting scheduled update **************************'); + AppLogger.info('[DataUpdateScheduler] Starting scheduled data update...'); startUpdateWorkers(); }); job.start(); diff --git a/v6y-apps/bfb-static-auditor/package.json b/v6y-apps/bfb-static-auditor/package.json index 861d5dc2..67364c9e 100644 --- a/v6y-apps/bfb-static-auditor/package.json +++ b/v6y-apps/bfb-static-auditor/package.json @@ -15,7 +15,7 @@ "scripts": { "build:tsc": "tsc --project ./tsconfig.json", "check:tsc": "tsc --noEmit --project ./tsconfig.json", - "start:dev": "nodemon --exec node --env-file=./.env --loader ts-node/esm src/index.ts -- --dev", + "start:dev": "NODE_ENV=development nodemon --exec node --env-file=./.env --loader ts-node/esm src/index.ts -- --dev", "start": "nodemon --exec node --env-file=./.env --loader ts-node/esm src/index.ts --", "code:analyze": "es6-plato -r -d target/report src", "lint": "eslint --config ./eslint.config.mjs --cache-location ./.eslintcache \"./**/*.ts\" --cache", diff --git a/v6y-apps/bfb-static-auditor/src/auditors/StaticAuditorManager.ts b/v6y-apps/bfb-static-auditor/src/auditors/StaticAuditorManager.ts index e659445b..30b3a75e 100644 --- a/v6y-apps/bfb-static-auditor/src/auditors/StaticAuditorManager.ts +++ b/v6y-apps/bfb-static-auditor/src/auditors/StaticAuditorManager.ts @@ -1,10 +1,8 @@ import { AppLogger, WorkerHelper } from '@v6y/core-logic'; -import ServerConfig from '../commons/ServerConfig.ts'; import { AuditCommonsType } from './types/AuditCommonsType.ts'; const { forkWorker } = WorkerHelper; -const { currentConfig } = ServerConfig; const startStaticAudit = async ({ applicationId, @@ -21,11 +19,10 @@ const startStaticAudit = async ({ // Start audits in a worker thread to prevent blocking the main thread const workerConfig = { - ...currentConfig, applicationId, workspaceFolder, auditRunId, - } as WorkerOptions; + }; await forkWorker('./src/workers/CodeQualityAnalysisWorker.ts', workerConfig); AppLogger.info( diff --git a/v6y-apps/bfb-static-auditor/src/auditors/bundle-analyzer/BundleAnalyzeAuditor.ts b/v6y-apps/bfb-static-auditor/src/auditors/bundle-analyzer/BundleAnalyzeAuditor.ts index 084129bf..46e32449 100644 --- a/v6y-apps/bfb-static-auditor/src/auditors/bundle-analyzer/BundleAnalyzeAuditor.ts +++ b/v6y-apps/bfb-static-auditor/src/auditors/bundle-analyzer/BundleAnalyzeAuditor.ts @@ -31,14 +31,15 @@ const startAuditorAnalysis = async ({ const auditReports = await startBundleAnalyzeReports({ workspaceFolder, applicationId }); - // Add auditRunId to each report - if (auditRunId) { - const auditRunIdNum = - typeof auditRunId === 'string' ? parseInt(auditRunId, 10) : auditRunId; - auditReports.forEach((audit) => { + // Add appId and auditRunId to each report + auditReports.forEach((audit) => { + audit.appId = applicationId; + if (auditRunId) { + const auditRunIdNum = + typeof auditRunId === 'string' ? parseInt(auditRunId, 10) : auditRunId; audit.auditRunId = auditRunIdNum; - }); - } + } + }); await AuditProvider.insertAuditList(auditReports); AppLogger.info( diff --git a/v6y-apps/bfb-static-auditor/src/auditors/code-complexity/CodeComplexityAuditor.ts b/v6y-apps/bfb-static-auditor/src/auditors/code-complexity/CodeComplexityAuditor.ts index 2f123c13..bb99f6bc 100644 --- a/v6y-apps/bfb-static-auditor/src/auditors/code-complexity/CodeComplexityAuditor.ts +++ b/v6y-apps/bfb-static-auditor/src/auditors/code-complexity/CodeComplexityAuditor.ts @@ -47,14 +47,15 @@ const startAuditorAnalysis = async ({ return false; } - // Add auditRunId to each report - if (auditRunId) { - const auditRunIdNum = - typeof auditRunId === 'string' ? parseInt(auditRunId, 10) : auditRunId; - auditReports.forEach((audit) => { + // Add appId and auditRunId to each report + auditReports.forEach((audit) => { + audit.appId = applicationId; + if (auditRunId) { + const auditRunIdNum = + typeof auditRunId === 'string' ? parseInt(auditRunId, 10) : auditRunId; audit.auditRunId = auditRunIdNum; - }); - } + } + }); await AuditProvider.insertAuditList(auditReports); diff --git a/v6y-apps/bfb-static-auditor/src/auditors/code-coupling/CodeCouplingAuditor.ts b/v6y-apps/bfb-static-auditor/src/auditors/code-coupling/CodeCouplingAuditor.ts index e7b84874..03333d69 100644 --- a/v6y-apps/bfb-static-auditor/src/auditors/code-coupling/CodeCouplingAuditor.ts +++ b/v6y-apps/bfb-static-auditor/src/auditors/code-coupling/CodeCouplingAuditor.ts @@ -49,14 +49,15 @@ const startAuditorAnalysis = async ({ workspaceFolder, }); - // Add auditRunId to each report - if (auditRunId) { - const auditRunIdNum = - typeof auditRunId === 'string' ? parseInt(auditRunId, 10) : auditRunId; - auditReports.forEach((audit) => { + // Add appId and auditRunId to each report + auditReports.forEach((audit) => { + audit.appId = applicationId; + if (auditRunId) { + const auditRunIdNum = + typeof auditRunId === 'string' ? parseInt(auditRunId, 10) : auditRunId; audit.auditRunId = auditRunIdNum; - }); - } + } + }); await AuditProvider.insertAuditList(auditReports); diff --git a/v6y-apps/bfb-static-auditor/src/auditors/code-duplication/CodeDuplicationAuditor.ts b/v6y-apps/bfb-static-auditor/src/auditors/code-duplication/CodeDuplicationAuditor.ts index 665ab026..8ec6e461 100644 --- a/v6y-apps/bfb-static-auditor/src/auditors/code-duplication/CodeDuplicationAuditor.ts +++ b/v6y-apps/bfb-static-auditor/src/auditors/code-duplication/CodeDuplicationAuditor.ts @@ -105,14 +105,15 @@ const startAuditorAnalysis = async ({ duplicationFiles: jscpdFileAnalysisResultJson?.duplicates, }); - // Add auditRunId to each report - if (auditRunId) { - const auditRunIdNum = - typeof auditRunId === 'string' ? parseInt(auditRunId, 10) : auditRunId; - auditReports.forEach((audit) => { + // Add appId and auditRunId to each report + auditReports.forEach((audit) => { + audit.appId = applicationId; + if (auditRunId) { + const auditRunIdNum = + typeof auditRunId === 'string' ? parseInt(auditRunId, 10) : auditRunId; audit.auditRunId = auditRunIdNum; - }); - } + } + }); await AuditProvider.insertAuditList(auditReports); diff --git a/v6y-apps/bfb-static-auditor/src/auditors/code-modularity/CodeModularityAuditor.ts b/v6y-apps/bfb-static-auditor/src/auditors/code-modularity/CodeModularityAuditor.ts index 859ff498..64a4f885 100644 --- a/v6y-apps/bfb-static-auditor/src/auditors/code-modularity/CodeModularityAuditor.ts +++ b/v6y-apps/bfb-static-auditor/src/auditors/code-modularity/CodeModularityAuditor.ts @@ -151,14 +151,15 @@ const startAuditorAnalysis = async ({ }, }); - // Add auditRunId to each report - if (auditRunId) { - const auditRunIdNum = - typeof auditRunId === 'string' ? parseInt(auditRunId, 10) : auditRunId; - auditReports.forEach((audit) => { + // Add appId and auditRunId to each report + auditReports?.forEach((audit) => { + audit.appId = applicationId; + if (auditRunId) { + const auditRunIdNum = + typeof auditRunId === 'string' ? parseInt(auditRunId, 10) : auditRunId; audit.auditRunId = auditRunIdNum; - }); - } + } + }); await AuditProvider.insertAuditList(auditReports); diff --git a/v6y-apps/bfb-static-auditor/src/auditors/code-security/CodeSecurityAuditor.ts b/v6y-apps/bfb-static-auditor/src/auditors/code-security/CodeSecurityAuditor.ts index e4d8211d..4a14c0a8 100644 --- a/v6y-apps/bfb-static-auditor/src/auditors/code-security/CodeSecurityAuditor.ts +++ b/v6y-apps/bfb-static-auditor/src/auditors/code-security/CodeSecurityAuditor.ts @@ -48,14 +48,15 @@ const startAuditorAnalysis = async ({ workspaceFolder, }); - // Add auditRunId to each report - if (auditRunId) { - const auditRunIdNum = - typeof auditRunId === 'string' ? parseInt(auditRunId, 10) : auditRunId; - securityAuditReports.forEach((audit) => { + // Add appId and auditRunId to each report + securityAuditReports.forEach((audit) => { + audit.appId = applicationId; + if (auditRunId) { + const auditRunIdNum = + typeof auditRunId === 'string' ? parseInt(auditRunId, 10) : auditRunId; audit.auditRunId = auditRunIdNum; - }); - } + } + }); await AuditProvider.insertAuditList(securityAuditReports); diff --git a/v6y-apps/bfb-static-auditor/src/auditors/ecological-impact/CreedengoAuditor.ts b/v6y-apps/bfb-static-auditor/src/auditors/ecological-impact/CreedengoAuditor.ts index 21357379..af9fe75b 100644 --- a/v6y-apps/bfb-static-auditor/src/auditors/ecological-impact/CreedengoAuditor.ts +++ b/v6y-apps/bfb-static-auditor/src/auditors/ecological-impact/CreedengoAuditor.ts @@ -63,14 +63,15 @@ const startAuditorAnalysis = async ({ return true; } - // Add auditRunId to each report - if (auditRunId) { - const auditRunIdNum = - typeof auditRunId === 'string' ? parseInt(auditRunId, 10) : auditRunId; - creedengoAuditReports.forEach((audit) => { + // Add appId and auditRunId to each report + creedengoAuditReports.forEach((audit) => { + audit.appId = applicationId; + if (auditRunId) { + const auditRunIdNum = + typeof auditRunId === 'string' ? parseInt(auditRunId, 10) : auditRunId; audit.auditRunId = auditRunIdNum; - }); - } + } + }); await AuditProvider.insertAuditList(creedengoAuditReports); diff --git a/v6y-apps/bff/package.json b/v6y-apps/bff/package.json index fa228e04..6d652c74 100644 --- a/v6y-apps/bff/package.json +++ b/v6y-apps/bff/package.json @@ -15,7 +15,7 @@ "scripts": { "build:tsc": "tsc --project ./tsconfig.json", "check:tsc": "tsc --noEmit --project ./tsconfig.json", - "start:dev": "nodemon --exec node --env-file=./.env --loader ts-node/esm src/index.ts -- --dev", + "start:dev": "NODE_ENV=development nodemon --exec node --env-file=./.env --loader ts-node/esm src/index.ts -- --dev", "start": "nodemon --exec node --env-file=./.env --loader ts-node/esm src/index.ts --", "lint": "eslint --config ./eslint.config.mjs --cache-location ./.eslintcache \"./**/*.ts\" --cache", "lint:fix": "eslint --config ./eslint.config.mjs --cache-location ./.eslintcache \"./**/*.ts\" --cache --fix", diff --git a/v6y-apps/bff/src/config/ServerConfig.ts b/v6y-apps/bff/src/config/ServerConfig.ts index 12691585..bc56284e 100644 --- a/v6y-apps/bff/src/config/ServerConfig.ts +++ b/v6y-apps/bff/src/config/ServerConfig.ts @@ -4,6 +4,9 @@ const V6Y_API_BASE_PATH = normalizeBasePath(process.env.V6Y_BFF_API_PATH); const V6Y_HEALTH_CHECK_PATH = `${V6Y_API_BASE_PATH}health-checks`; const V6Y_MONITORING_PATH = `${V6Y_API_BASE_PATH}monitoring`; +const V6Y_MAIN_API_PATH = normalizeBasePath(process.env.V6Y_MAIN_API_PATH); +const MAIN_ANALYZER_API_PATH = `${V6Y_MAIN_API_PATH}trigger-audit`; + const SERVER_ENV_CONFIGURATION = { production: { ssl: false, @@ -12,6 +15,8 @@ const SERVER_ENV_CONFIGURATION = { apiPath: process.env.V6Y_BFF_API_PATH, healthCheckPath: V6Y_HEALTH_CHECK_PATH, monitoringPath: V6Y_MONITORING_PATH, + mainAnalyzerApiPath: MAIN_ANALYZER_API_PATH, + databaseUri: '', serverTimeout: 900000, // milliseconds }, development: { @@ -21,6 +26,8 @@ const SERVER_ENV_CONFIGURATION = { apiPath: process.env.V6Y_BFF_API_PATH, healthCheckPath: V6Y_HEALTH_CHECK_PATH, monitoringPath: V6Y_MONITORING_PATH, + mainAnalyzerApiPath: MAIN_ANALYZER_API_PATH, + databaseUri: '', serverTimeout: 900000, // milliseconds }, } as ServerEnvConfigType; diff --git a/v6y-apps/bff/src/resolvers/application/ApplicationMutations.ts b/v6y-apps/bff/src/resolvers/application/ApplicationMutations.ts index f2b9b72a..5b84d85c 100644 --- a/v6y-apps/bff/src/resolvers/application/ApplicationMutations.ts +++ b/v6y-apps/bff/src/resolvers/application/ApplicationMutations.ts @@ -2,58 +2,30 @@ import { AppLogger, ApplicationInputType, ApplicationProvider, - AuditRunProvider, SearchQueryType, } from '@v6y/core-logic'; -/** - * Trigger static auditor for application - */ -const triggerStaticAuditor = async (applicationId: number, auditRunId: string) => { - try { - const staticAuditorUrl = - process.env.V6Y_STATIC_AUDITOR_API_PATH || 'http://bfb-static-auditor:3001/audits'; - await fetch(staticAuditorUrl, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ applicationId, auditRunId }), - }); - } catch (err) { - AppLogger.warn(`[AppMutations - triggerApplicationAnalysis] Static auditor error: ${err}`); - } -}; +import ServerConfig from '../../config/ServerConfig.ts'; -/** - * Trigger dynamic auditor for application - */ -const triggerDynamicAuditor = async (applicationId: number, auditRunId: string) => { - try { - const dynamicAuditorUrl = - process.env.V6Y_DYNAMIC_AUDITOR_API_PATH || 'http://bfb-dynamic-auditor:3002/audits'; - await fetch(dynamicAuditorUrl, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ applicationId, auditRunId, workspaceFolder: null }), - }); - } catch (err) { - AppLogger.warn(`[AppMutations - triggerApplicationAnalysis] Dynamic auditor error: ${err}`); - } -}; +const { currentConfig } = ServerConfig; /** - * Trigger devops auditor for application + * Trigger audit analysis via main-analyzer */ -const triggerDevopsAuditor = async (applicationId: number, auditRunId: string) => { +const triggerAuditAnalysis = async ( + applicationId: number, + branch: string | undefined, + analysisTypes: string[], +) => { try { - const devopsAuditorUrl = - process.env.V6Y_DEVOPS_AUDITOR_API_PATH || 'http://bfb-devops-auditor:3003/audits'; - await fetch(devopsAuditorUrl, { + const mainAnalyzerUrl = currentConfig?.mainAnalyzerApiPath; + await fetch(mainAnalyzerUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ applicationId, auditRunId }), + body: JSON.stringify({ applicationId, branch, analysisTypes }), }); } catch (err) { - AppLogger.warn(`[AppMutations - triggerApplicationAnalysis] DevOps auditor error: ${err}`); + AppLogger.warn(`[AppMutations - triggerApplicationAnalysis] Main analyzer error: ${err}`); } }; @@ -99,10 +71,10 @@ const createOrEditApplication = async ( `[AppMutations - createOrEditApplication] gitOrganization : ${gitOrganization}`, ); AppLogger.info( - `[AppMutations - createOrEditApplication] dataDogApiKey : ${dataDogApiKey ? '"********"}`,' : 'null'}`, + `[AppMutations - createOrEditApplication] dataDogApiKey : ${dataDogApiKey ? '"********"' : 'null'}`, ); AppLogger.info( - `[AppMutations - createOrEditApplication] dataDogAppKey : ${dataDogAppKey ? '"********"}`,' : 'null'}`, + `[AppMutations - createOrEditApplication] dataDogAppKey : ${dataDogAppKey ? '"********"' : 'null'}`, ); AppLogger.info(`[AppMutations - createOrEditApplication] dataDogUrl : ${dataDogUrl}`); AppLogger.info( @@ -233,8 +205,6 @@ const triggerApplicationAnalysis = async ( }; }, ) => { - let auditRunId: string | undefined; - try { const { applicationId, branch, analysisTypes } = params?.input || {}; @@ -273,81 +243,21 @@ const triggerApplicationAnalysis = async ( }; } - // Create audit run record - const auditRun = await AuditRunProvider.createAuditRun({ - appId: applicationId, - branch: branch || null, - runStatus: 'pending', - analysisTypes, - }); - - if (!auditRun) { - AppLogger.error( - '[AppMutations - triggerApplicationAnalysis] Failed to create audit run', - ); - return { - success: false, - message: 'Failed to create audit run', - applicationId, - branch, - auditRun: null, - }; - } - - auditRunId = auditRun._id; AppLogger.info( - `[AppMutations - triggerApplicationAnalysis] Audit run created: ${auditRunId}`, + `[AppMutations - triggerApplicationAnalysis] applicationId: ${applicationId}, branch: ${branch}, analysisTypes: ${analysisTypes.join(',')}`, ); - // Update status to in_progress - await AuditRunProvider.updateAuditRunStatus({ - auditRunId, - runStatus: 'in_progress', - }); - - // Trigger async analysis job via API calls + // Trigger async analysis job via main-analyzer (async () => { try { - const branchesToAnalyze = branch - ? [{ name: branch }] - : application?.repo?.allBranches?.map((b: string) => ({ name: b })) || []; - - // Trigger static auditor if included - if (analysisTypes.includes('static') && branchesToAnalyze.length > 0) { - AppLogger.info( - `[AppMutations - triggerApplicationAnalysis] Triggering static auditor for app ${applicationId}`, - ); - await triggerStaticAuditor(applicationId, auditRunId); - } - - // Trigger dynamic auditor if included - if (analysisTypes.includes('dynamic')) { - AppLogger.info( - `[AppMutations - triggerApplicationAnalysis] Triggering dynamic auditor for app ${applicationId}`, - ); - await triggerDynamicAuditor(applicationId, auditRunId); - } - - // Trigger devops auditor if included - if (analysisTypes.includes('devops')) { - AppLogger.info( - `[AppMutations - triggerApplicationAnalysis] Triggering devops auditor for app ${applicationId}`, - ); - await triggerDevopsAuditor(applicationId, auditRunId); - } + AppLogger.info( + `[AppMutations - triggerApplicationAnalysis] Triggering main-analyzer for app ${applicationId}`, + ); + await triggerAuditAnalysis(applicationId, branch, analysisTypes); } catch (asyncError) { AppLogger.error( `[AppMutations - triggerApplicationAnalysis] Async analysis job error: ${asyncError}`, ); - await AuditRunProvider.updateAuditRunStatus({ - auditRunId: auditRunId!, - runStatus: 'error', - errorMessage: String(asyncError), - }).catch((updateErr) => - AppLogger.warn( - `[AppMutations - triggerApplicationAnalysis] Failed to update error status: ${updateErr}`, - ), - ); } })(); @@ -356,24 +266,11 @@ const triggerApplicationAnalysis = async ( message: 'Analysis triggered successfully', applicationId, branch, - auditRun, + auditRun: null, }; } catch (error) { AppLogger.error(`[AppMutations - triggerApplicationAnalysis] error: ${error}`); - // Try to update run status to error if it was created - if (auditRunId) { - await AuditRunProvider.updateAuditRunStatus({ - auditRunId, - runStatus: 'error', - errorMessage: String(error), - }).catch((updateErr) => - AppLogger.warn( - `[AppMutations - triggerApplicationAnalysis] Failed to update error status: ${updateErr}`, - ), - ); - } - return { success: false, message: `Error triggering analysis: ${error}`, diff --git a/v6y-libs/core-logic/src/config/ServerConfig.ts b/v6y-libs/core-logic/src/config/ServerConfig.ts index 75222f17..3c1e64d8 100644 --- a/v6y-libs/core-logic/src/config/ServerConfig.ts +++ b/v6y-libs/core-logic/src/config/ServerConfig.ts @@ -21,9 +21,17 @@ export const CorsOptions = { export const getServerConfig = ( SERVER_ENV_CONFIGURATION: ServerEnvConfigType, ): ServerConfigType => { + // Check multiple sources for dev mode detection: + // 1. NODE_ENV environment variable (most reliable) + // 2. --dev command line flag (for backward compatibility) + const nodeEnv = process.env.NODE_ENV; const execEnv = process?.argv; - const currentContext = execEnv?.includes('--dev') ? 'development' : 'production'; + const hasDevFlag = execEnv?.includes('--dev'); + const currentContext = nodeEnv === 'development' || hasDevFlag ? 'development' : 'production'; + + AppLogger.info(`[getServerConfig] NODE_ENV: ${nodeEnv}`); + AppLogger.info(`[getServerConfig] process.argv: ${JSON.stringify(execEnv)}`); AppLogger.info(`[getServerConfig] currentContext: ${currentContext}`); const currentConfig = SERVER_ENV_CONFIGURATION[currentContext]; From 62be4582aba693d1e37b87e4a6f5e61975592de0 Mon Sep 17 00:00:00 2001 From: "theo.gillet" Date: Tue, 26 May 2026 11:38:34 +0200 Subject: [PATCH 3/7] [FIX]: support local and containerized database connections --- docker-compose.yml | 3 ++- env-template | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index e4cba276..e2c9edaa 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -22,11 +22,12 @@ services: context: . dockerfile: v6y-config/Dockerfile.migrations environment: - - PSQL_DB_HOST=${PSQL_DB_HOST} + - PSQL_DB_HOST=v6y-database - PSQL_DB_NAME=${PSQL_DB_NAME} - PSQL_DB_USER=${PSQL_DB_USER} - PSQL_DB_PASSWORD=${PSQL_DB_PASSWORD} - PSQL_DB_PORT=${PSQL_DB_PORT} + - DATABASE_URL=postgresql://${PSQL_DB_USER}:${PSQL_DB_PASSWORD}@v6y-database:${PSQL_DB_PORT}/${PSQL_DB_NAME} depends_on: v6y-database: condition: service_healthy diff --git a/env-template b/env-template index 8dd19f57..83659cbf 100644 --- a/env-template +++ b/env-template @@ -1,9 +1,9 @@ -PSQL_DB_HOST=x.x.x.x +PSQL_DB_HOST=localhost PSQL_DB_NAME=x PSQL_DB_USER=x PSQL_DB_PASSWORD=x PSQL_DB_PORT= -DATABASE_URL=postgresql://user:password@host:port/database +DATABASE_URL=postgresql://user:password@localhost:port/database SUPERADMIN_USERNAME=x SUPERADMIN_EMAIL=x From 61f8255314f3d02bf54c2a6b0d62201d1524c77a Mon Sep 17 00:00:00 2001 From: "theo.gillet" Date: Tue, 26 May 2026 11:48:10 +0200 Subject: [PATCH 4/7] [FIX]: debug log CORS --- v6y-libs/core-logic/src/config/ServerConfig.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/v6y-libs/core-logic/src/config/ServerConfig.ts b/v6y-libs/core-logic/src/config/ServerConfig.ts index 3c1e64d8..97d78dae 100644 --- a/v6y-libs/core-logic/src/config/ServerConfig.ts +++ b/v6y-libs/core-logic/src/config/ServerConfig.ts @@ -9,7 +9,9 @@ export const CorsOptions = { origin: string | undefined, callback: (err: Error | null, origin?: string) => void, ) { - AppLogger.debug(`[getServerConfig] CORS origin redirect: ${origin}`); + if (origin) { + AppLogger.debug(`[getServerConfig] CORS origin redirect: ${origin}`); + } callback(null, origin); }, }; From 63741e44c522f8e1729bd88c6bae2d95555e5935 Mon Sep 17 00:00:00 2001 From: "theo.gillet" Date: Tue, 26 May 2026 14:54:10 +0200 Subject: [PATCH 5/7] [FIX]: add ts-node/esm loader to worker threads for dynamic auditor --- docker-compose.yml | 4 ++ .../src/auditors/DynamicAuditorManager.ts | 62 +++++++++++------ v6y-apps/bfb-main-analyzer/src/app.ts | 2 +- .../src/config/ServerConfig.ts | 7 ++ v6y-libs/core-logic/src/core/WorkerHelper.ts | 6 ++ .../src/database/AuditRunProvider.ts | 67 +++++++++++++++++++ 6 files changed, 128 insertions(+), 20 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index e2c9edaa..36d9a8d0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -86,7 +86,11 @@ services: - V6Y_MAIN_API_PATH=${V6Y_MAIN_API_PATH} - V6Y_MAIN_API_PORT=${V6Y_MAIN_API_PORT} - V6Y_STATIC_ANALYZER_API_PATH=${V6Y_STATIC_ANALYZER_API_PATH} + - V6Y_STATIC_ANALYZER_API_PORT=${V6Y_STATIC_ANALYZER_API_PORT} - V6Y_DYNAMIC_ANALYZER_API_PATH=${V6Y_DYNAMIC_ANALYZER_API_PATH} + - V6Y_DYNAMIC_ANALYZER_API_PORT=${V6Y_DYNAMIC_ANALYZER_API_PORT} + - V6Y_DEVOPS_API_PATH=${V6Y_DEVOPS_API_PATH} + - V6Y_DEVOPS_API_PORT=${V6Y_DEVOPS_API_PORT} depends_on: v6y-migrate: condition: service_completed_successfully diff --git a/v6y-apps/bfb-dynamic-auditor/src/auditors/DynamicAuditorManager.ts b/v6y-apps/bfb-dynamic-auditor/src/auditors/DynamicAuditorManager.ts index c13f76b4..98414f3c 100644 --- a/v6y-apps/bfb-dynamic-auditor/src/auditors/DynamicAuditorManager.ts +++ b/v6y-apps/bfb-dynamic-auditor/src/auditors/DynamicAuditorManager.ts @@ -19,32 +19,56 @@ const startDynamicAudit = async ({ applicationId, auditRunId }: AuditCommonsType }; // start Lighthouse analysis - await forkWorker( - './src/workers/LighthouseAnalysisWorker.ts', - workerConfig as WorkerOptions, - ); - AppLogger.info( - '[DynamicAuditorManager - startDynamicAudit] Lighthouse Audit have completed successfully.', - ); + try { + await forkWorker( + './src/workers/LighthouseAnalysisWorker.ts', + workerConfig as WorkerOptions, + ); + AppLogger.info( + '[DynamicAuditorManager - startDynamicAudit] Lighthouse Audit have completed successfully.', + ); + } catch (lighthouseError) { + AppLogger.error( + '[DynamicAuditorManager - startDynamicAudit] Lighthouse worker error:', + lighthouseError, + ); + } // start Green Hosting analysis - await forkWorker( - './src/workers/GreenHostingAnalysisWorker.ts', - workerConfig as WorkerOptions, - ); - AppLogger.info( - '[DynamicAuditorManager - startDynamicAudit] Green Hosting Audit have completed successfully.', - ); + try { + await forkWorker( + './src/workers/GreenHostingAnalysisWorker.ts', + workerConfig as WorkerOptions, + ); + AppLogger.info( + '[DynamicAuditorManager - startDynamicAudit] Green Hosting Audit have completed successfully.', + ); + } catch (greenError) { + AppLogger.error( + '[DynamicAuditorManager - startDynamicAudit] Green Hosting worker error:', + greenError, + ); + } // start SonarQube analysis - await forkWorker('./src/workers/SonarQubeAnalysisWorker.ts', workerConfig as WorkerOptions); - AppLogger.info( - '[DynamicAuditorManager - startDynamicAudit] SonarQube Audit have completed successfully.', - ); + try { + await forkWorker( + './src/workers/SonarQubeAnalysisWorker.ts', + workerConfig as WorkerOptions, + ); + AppLogger.info( + '[DynamicAuditorManager - startDynamicAudit] SonarQube Audit have completed successfully.', + ); + } catch (sonarError) { + AppLogger.error( + '[DynamicAuditorManager - startDynamicAudit] SonarQube worker error:', + sonarError, + ); + } return true; // Indicates successful initiation of audits } catch (error) { - AppLogger.info( + AppLogger.error( '[DynamicAuditorManager - startDynamicAudit] An exception occurred during the app audits: ', error, ); diff --git a/v6y-apps/bfb-main-analyzer/src/app.ts b/v6y-apps/bfb-main-analyzer/src/app.ts index 94a903ae..14462ef8 100644 --- a/v6y-apps/bfb-main-analyzer/src/app.ts +++ b/v6y-apps/bfb-main-analyzer/src/app.ts @@ -94,7 +94,7 @@ export function createApp(): ExpressApp { return; } - const auditRunId = String(auditRun._id); + const auditRunId = Number(auditRun._id); // Update status to in_progress await AuditRunProvider.updateAuditRunStatus({ diff --git a/v6y-apps/bfb-main-analyzer/src/config/ServerConfig.ts b/v6y-apps/bfb-main-analyzer/src/config/ServerConfig.ts index 48f18e59..ac602ff4 100644 --- a/v6y-apps/bfb-main-analyzer/src/config/ServerConfig.ts +++ b/v6y-apps/bfb-main-analyzer/src/config/ServerConfig.ts @@ -19,6 +19,13 @@ const buildAuditorUrl = ( AppLogger.warn(`[buildAuditorUrl] Missing apiPath: ${apiPath} or port: ${port}`); return ''; } + + // If apiPath is already a full URL, use it directly + if (apiPath.startsWith('http://') || apiPath.startsWith('https://')) { + AppLogger.debug(`[buildAuditorUrl] apiPath is already a full URL: ${apiPath}`); + return apiPath; + } + const normalizedPath = normalizeBasePath(apiPath); const normalizedEndpoint = endpoint.startsWith('/') ? endpoint : `/${endpoint}`; const fullUrl = `http://localhost:${port}${normalizedPath}${normalizedEndpoint}`; diff --git a/v6y-libs/core-logic/src/core/WorkerHelper.ts b/v6y-libs/core-logic/src/core/WorkerHelper.ts index 454eac69..4aa2b2d4 100644 --- a/v6y-libs/core-logic/src/core/WorkerHelper.ts +++ b/v6y-libs/core-logic/src/core/WorkerHelper.ts @@ -9,6 +9,12 @@ const forkWorker = (filepath: string, workerData: WorkerOptions) => { return new Promise((resolve, reject) => { const worker = new Worker(filepath, { workerData, + // Configure worker to use ts-node/esm loader for TypeScript files + execArgv: ['--loader', 'ts-node/esm'], + env: { + ...process.env, + NODE_OPTIONS: '--loader ts-node/esm', + }, }); worker.on('online', () => { console.log('******************** Launching intensive CPU task ******************** '); diff --git a/v6y-libs/core-logic/src/database/AuditRunProvider.ts b/v6y-libs/core-logic/src/database/AuditRunProvider.ts index 3729ec40..e77912f6 100644 --- a/v6y-libs/core-logic/src/database/AuditRunProvider.ts +++ b/v6y-libs/core-logic/src/database/AuditRunProvider.ts @@ -16,6 +16,27 @@ const createAuditRun = async (auditRun: AuditRunType) => { return null; } + // Check if there's a recent pending run for the same app/branch (within last 5 minutes) + // to avoid creating duplicates + const recentRun = await getPrismaClient().auditRun.findFirst({ + where: { + appId: auditRun.appId, + branch: auditRun.branch ?? null, + runStatus: 'pending', + triggeredAt: { + gte: new Date(Date.now() - 5 * 60 * 1000), // Last 5 minutes + }, + }, + orderBy: { triggeredAt: 'desc' }, + }); + + if (recentRun) { + AppLogger.info( + '[AuditRunProvider - createAuditRun] Reusing recent run: ' + recentRun.id, + ); + return { ...recentRun, _id: recentRun.id }; + } + const created = await getPrismaClient().auditRun.create({ data: { appId: auditRun.appId, @@ -70,6 +91,50 @@ const updateAuditRunStatus = async ({ } }; +/** + * Complete an audit run after all audits are done + */ +const completeAuditRun = async (auditRunId: number, hasErrors: boolean = false) => { + try { + AppLogger.info( + '[AuditRunProvider - completeAuditRun] auditRunId: ' + + auditRunId + + ' hasErrors: ' + + hasErrors, + ); + + const completed = await updateAuditRunStatus({ + auditRunId, + runStatus: hasErrors ? 'failed' : 'completed', + completedAt: new Date(), + errorMessage: hasErrors ? 'Audit completed with errors' : null, + }); + + AppLogger.info('[AuditRunProvider - completeAuditRun] completed: ' + auditRunId); + return completed; + } catch (error) { + AppLogger.error('[AuditRunProvider - completeAuditRun] error: ', error); + return null; + } +}; + +/** + * Get audit run with all its audits to check completion + */ +const getAuditRunWithAudits = async (auditRunId: number) => { + try { + const auditRun = await getPrismaClient().auditRun.findUnique({ + where: { id: auditRunId }, + include: { audits: true }, + }); + + return auditRun ? { ...auditRun, _id: auditRun.id } : null; + } catch (error) { + AppLogger.error('[AuditRunProvider - getAuditRunWithAudits] error: ', error); + return null; + } +}; + const getAuditRunById = async (auditRunId: number) => { try { const auditRun = await getPrismaClient().auditRun.findUnique({ @@ -161,6 +226,8 @@ const deleteAuditRun = async (auditRunId: number) => { const AuditRunProvider = { createAuditRun, updateAuditRunStatus, + completeAuditRun, + getAuditRunWithAudits, getAuditRunById, getAuditRunsByApplicationId, getLatestAuditRun, From abaa7859ea972e6583c800acc4122816b38e9081 Mon Sep 17 00:00:00 2001 From: "theo.gillet" Date: Tue, 26 May 2026 16:22:19 +0200 Subject: [PATCH 6/7] feat: implement audit run history feature --- .../application/ApplicationQueries.ts | 48 +++ .../application/ApplicationQueriesType.ts | 1 + v6y-apps/bff/src/types/audit/AuditRunType.ts | 2 +- v6y-apps/front/public/locales/en/common.json | 29 ++ v6y-apps/front/public/locales/fr/common.json | 29 ++ .../api/getApplicationAuditRuns.ts | 23 ++ .../components/VitalityAppDetailsView.tsx | 11 + .../audit-runs/VitalityAuditRunHistory.tsx | 285 ++++++++++++++++++ .../VitalityAuditRunHistoryView.tsx | 77 +++++ .../components/audit-runs/index.ts | 2 + .../src/database/AuditRunProvider.ts | 18 +- 11 files changed, 523 insertions(+), 2 deletions(-) create mode 100644 v6y-apps/front/src/features/app-details/api/getApplicationAuditRuns.ts create mode 100644 v6y-apps/front/src/features/app-details/components/audit-runs/VitalityAuditRunHistory.tsx create mode 100644 v6y-apps/front/src/features/app-details/components/audit-runs/VitalityAuditRunHistoryView.tsx create mode 100644 v6y-apps/front/src/features/app-details/components/audit-runs/index.ts diff --git a/v6y-apps/bff/src/resolvers/application/ApplicationQueries.ts b/v6y-apps/bff/src/resolvers/application/ApplicationQueries.ts index fa058072..5cc6431d 100644 --- a/v6y-apps/bff/src/resolvers/application/ApplicationQueries.ts +++ b/v6y-apps/bff/src/resolvers/application/ApplicationQueries.ts @@ -83,6 +83,53 @@ const getApplicationDetailsAuditReportsByParams = async ( } }; +/** + * Get application audit runs history by params + * @param _ + * @param args + * @param user + */ +const getApplicationAuditRunsByParams = async ( + _: unknown, + args: ApplicationType, + { user }: { user: AccountType }, +) => { + try { + const { _id } = args || {}; + + AppLogger.info(`[ApplicationQueries - getApplicationAuditRuns] Received args:`, args); + AppLogger.info(`[ApplicationQueries - getApplicationAuditRuns] _id : ${_id}`); + + if (!_id) { + AppLogger.error('[ApplicationQueries - getApplicationAuditRuns] Missing _id parameter'); + return []; + } + + if (!(user.role === 'ADMIN' || user.role === 'SUPERADMIN')) { + const userApplicationsIds = user.applications || []; + if (!userApplicationsIds.includes(_id)) { + AppLogger.warn( + `[ApplicationQueries - getApplicationAuditRuns] Unauthorized access attempt for app ${_id}`, + ); + throw new Error('Unauthorized'); + } + } + + const auditRuns = await AuditRunProvider.getAuditRunsByAppId(_id); + + AppLogger.info( + `[ApplicationQueries - getApplicationAuditRuns] Found ${auditRuns?.length} audit runs for app ${_id}`, + ); + + return auditRuns || []; + } catch (error) { + AppLogger.error( + `[ApplicationQueries - getApplicationAuditRuns] error for app ${args?._id} : ${error}`, + ); + return []; + } +}; + /** * Get application details evolutions by params * @param _ @@ -486,6 +533,7 @@ const getAuditRunDetailsByParams = async ( const ApplicationQueries = { getApplicationDetailsInfoByParams, getApplicationDetailsAuditReportsByParams, + getApplicationAuditRunsByParams, getApplicationDetailsEvolutionsByParams, getApplicationDetailsDependenciesByParams, getApplicationDetailsKeywordsByParams, diff --git a/v6y-apps/bff/src/types/application/ApplicationQueriesType.ts b/v6y-apps/bff/src/types/application/ApplicationQueriesType.ts index e141eeba..20a79ffc 100644 --- a/v6y-apps/bff/src/types/application/ApplicationQueriesType.ts +++ b/v6y-apps/bff/src/types/application/ApplicationQueriesType.ts @@ -5,6 +5,7 @@ const ApplicationQueriesType = ` getApplicationTotalByParams(keywords: [String], searchText: String): Int getApplicationDetailsInfoByParams(_id: Int!): ApplicationType getApplicationDetailsAuditReportsByParams(_id: Int!): [AuditReportType] + getApplicationAuditRunsByParams(_id: Int!): [AuditRunType] getApplicationDetailsEvolutionsByParams(_id: Int!): [EvolutionType] getApplicationDetailsDependenciesByParams(_id: Int!): [DependencyType] getApplicationDetailsKeywordsByParams(_id: Int): [KeywordType] diff --git a/v6y-apps/bff/src/types/audit/AuditRunType.ts b/v6y-apps/bff/src/types/audit/AuditRunType.ts index b591d8ff..69e62d9f 100644 --- a/v6y-apps/bff/src/types/audit/AuditRunType.ts +++ b/v6y-apps/bff/src/types/audit/AuditRunType.ts @@ -16,7 +16,7 @@ const AuditRunType = ` analysisTypes: [String!]! """ Audit reports generated in this run """ - audits: [AuditReportType!]! + audits: [AuditReportType] """ When the audit run was triggered """ triggeredAt: String! diff --git a/v6y-apps/front/public/locales/en/common.json b/v6y-apps/front/public/locales/en/common.json index e48dd2fa..971a7e30 100644 --- a/v6y-apps/front/public/locales/en/common.json +++ b/v6y-apps/front/public/locales/en/common.json @@ -211,6 +211,35 @@ "title": "All Dependencies Up to Date", "description": "No outdated dependencies with security or compatibility issues have been found" } + }, + "auditHistory": { + "title": "Audit History", + "description": "Complete list of audit runs with statuses, dates and details", + "emptyMessage": "No audits have been executed for this application", + "table": { + "columns": { + "status": "Status", + "analysisTypes": "Analysis Types", + "triggered": "Triggered", + "duration": "Duration", + "audits": "Audits" + } + }, + "statuses": { + "pending": "Pending", + "in_progress": "In Progress", + "completed": "Completed", + "failed": "Failed", + "error": "Error" + }, + "mobile": { + "status": "Status", + "date": "Date", + "types": "Types", + "duration": "Duration", + "audits": "Audits", + "error": "Error" + } } }, "appStatsPage": { diff --git a/v6y-apps/front/public/locales/fr/common.json b/v6y-apps/front/public/locales/fr/common.json index 65b8045f..48b62cc2 100644 --- a/v6y-apps/front/public/locales/fr/common.json +++ b/v6y-apps/front/public/locales/fr/common.json @@ -211,6 +211,35 @@ "title": "Toutes les Dépendances sont à Jour", "description": "Aucune dépendance obsolète avec des problèmes de sécurité ou de compatibilité n'a été trouvée" } + }, + "auditHistory": { + "title": "Historique des audits", + "description": "Liste complète des exécutions d'audit avec statuts, dates et détails", + "emptyMessage": "Aucun audit n'a été exécuté pour cette application", + "table": { + "columns": { + "status": "Statut", + "analysisTypes": "Types d'analyse", + "triggered": "Déclenché", + "duration": "Durée", + "audits": "Audits" + } + }, + "statuses": { + "pending": "En attente", + "in_progress": "En cours", + "completed": "Complété", + "failed": "Échoué", + "error": "Erreur" + }, + "mobile": { + "status": "Statut", + "date": "Date", + "types": "Types", + "duration": "Durée", + "audits": "Audits", + "error": "Erreur" + } } }, "appStatsPage": { diff --git a/v6y-apps/front/src/features/app-details/api/getApplicationAuditRuns.ts b/v6y-apps/front/src/features/app-details/api/getApplicationAuditRuns.ts new file mode 100644 index 00000000..e49a6dd1 --- /dev/null +++ b/v6y-apps/front/src/features/app-details/api/getApplicationAuditRuns.ts @@ -0,0 +1,23 @@ +import { gql } from 'graphql-request'; + +const GetApplicationAuditRuns = gql` + query getApplicationAuditRunsByParams($_id: Int!) { + getApplicationAuditRunsByParams(_id: $_id) { + _id + appId + branch + runStatus + analysisTypes + triggeredAt + completedAt + errorMessage + audits { + _id + type + category + } + } + } +`; + +export default GetApplicationAuditRuns; diff --git a/v6y-apps/front/src/features/app-details/components/VitalityAppDetailsView.tsx b/v6y-apps/front/src/features/app-details/components/VitalityAppDetailsView.tsx index 3bc7d68a..b78ca468 100644 --- a/v6y-apps/front/src/features/app-details/components/VitalityAppDetailsView.tsx +++ b/v6y-apps/front/src/features/app-details/components/VitalityAppDetailsView.tsx @@ -30,6 +30,10 @@ const VitalitySecuritySection = DynamicLoader( const VitalitySonarQubeView = DynamicLoader(() => import('./sonarqube/VitalitySonarQubeView')); +const VitalityAuditRunHistoryView = DynamicLoader( + () => import('./audit-runs/VitalityAuditRunHistoryView'), +); + const extractProjectKeyFromSonarUrl = (url: string): string | null => { try { const urlObj = new URL(url); @@ -326,6 +330,13 @@ const VitalityAppDetailsView = () => { {renderTabContent()} + +
+

+ {translate('vitality.appDetailsPage.auditHistory.title')} +

+ +
diff --git a/v6y-apps/front/src/features/app-details/components/audit-runs/VitalityAuditRunHistory.tsx b/v6y-apps/front/src/features/app-details/components/audit-runs/VitalityAuditRunHistory.tsx new file mode 100644 index 00000000..e0b8a68b --- /dev/null +++ b/v6y-apps/front/src/features/app-details/components/audit-runs/VitalityAuditRunHistory.tsx @@ -0,0 +1,285 @@ +import React from 'react'; + +import { useTranslationProvider } from '@v6y/ui-kit'; + +interface AuditRunType { + _id: number; + runStatus: string; + analysisTypes: string[]; + triggeredAt: string; + completedAt: string | null; + errorMessage: string | null; + audits?: Array<{ _id: number; type: string; category: string }>; +} + +interface VitalityAuditRunHistoryProps { + auditRuns: AuditRunType[]; + isLoading?: boolean; +} + +const Badge = ({ + children, + variant = 'info', + className = '', +}: { + children: React.ReactNode; + variant?: 'success' | 'warning' | 'error' | 'info'; + className?: string; +}) => { + const variantClasses = { + success: 'bg-green-100 text-green-800 border border-green-200', + warning: 'bg-amber-100 text-amber-800 border border-amber-200', + error: 'bg-red-100 text-red-800 border border-red-200', + info: 'bg-blue-100 text-blue-800 border border-blue-200', + }; + + return ( + + {children} + + ); +}; + +const getStatusColor = (status: string) => { + switch (status) { + case 'completed': + return 'bg-green-50 border-green-200'; + case 'in_progress': + return 'bg-blue-50 border-blue-200'; + case 'pending': + return 'bg-amber-50 border-amber-200'; + case 'error': + case 'failed': + return 'bg-red-50 border-red-200'; + default: + return 'bg-slate-50 border-slate-200'; + } +}; + +const getStatusBadgeVariant = (status: string): 'success' | 'warning' | 'error' | 'info' => { + switch (status) { + case 'completed': + return 'success'; + case 'in_progress': + return 'info'; + case 'pending': + return 'warning'; + case 'error': + case 'failed': + return 'error'; + default: + return 'info'; + } +}; + +const formatDate = (dateString: string) => { + const date = new Date(dateString); + return date.toLocaleDateString('en-US', { + month: 'short', + day: 'numeric', + year: 'numeric', + hour: '2-digit', + minute: '2-digit', + }); +}; + +const getDuration = (startDate: string, endDate: string | null) => { + if (!endDate) return null; + const start = new Date(startDate); + const end = new Date(endDate); + const durationMs = end.getTime() - start.getTime(); + const seconds = Math.floor(durationMs / 1000); + + if (seconds < 60) return `${seconds}s`; + if (seconds < 3600) return `${Math.floor(seconds / 60)}m`; + return `${Math.floor(seconds / 3600)}h ${Math.floor((seconds % 3600) / 60)}m`; +}; + +export const VitalityAuditRunHistory: React.FC = ({ + auditRuns = [], + isLoading = false, +}) => { + const { translate } = useTranslationProvider(); + + if (isLoading) { + return ( +
+ {translate('vitality.appDetailsPage.loadingStates.auditReports')} +
+ ); + } + + if (!auditRuns || auditRuns.length === 0) { + return ( +
+

+ {translate('vitality.appDetailsPage.auditHistory.emptyMessage')} +

+
+ ); + } + + return ( +
+ {/* Mobile: Card View */} +
+
+ {auditRuns.map((run, index) => ( +
+ {/* Header with status and date */} +
+ + {translate( + `vitality.appDetailsPage.auditHistory.statuses.${run.runStatus}`, + ) || run.runStatus} + + + {formatDate(run.triggeredAt)} + +
+ + {/* Analysis types */} + {run.analysisTypes && run.analysisTypes.length > 0 && ( +
+

+ {translate( + 'vitality.appDetailsPage.auditHistory.mobile.types', + )} + : +

+
+ {run.analysisTypes.map((type, idx) => ( + + {type} + + ))} +
+
+ )} + + {/* Duration */} + {getDuration(run.triggeredAt, run.completedAt) && ( +

+ + {translate( + 'vitality.appDetailsPage.auditHistory.mobile.duration', + )} + + : {getDuration(run.triggeredAt, run.completedAt)} +

+ )} + + {/* Audit count */} + {run.audits && run.audits.length > 0 && ( +

+ + {translate( + 'vitality.appDetailsPage.auditHistory.mobile.audits', + )} + + : {run.audits.length} +

+ )} + + {/* Error message */} + {run.errorMessage && ( +
+

+ {translate( + 'vitality.appDetailsPage.auditHistory.mobile.error', + )} + : +

+

{run.errorMessage}

+
+ )} +
+ ))} +
+
+ + {/* Desktop: Table View */} +
+ + + + + + + + + + + + {auditRuns.map((run, index) => ( + + + + + + + + ))} + +
+ {translate( + 'vitality.appDetailsPage.auditHistory.table.columns.status', + )} + + {translate( + 'vitality.appDetailsPage.auditHistory.table.columns.analysisTypes', + )} + + {translate( + 'vitality.appDetailsPage.auditHistory.table.columns.triggered', + )} + + {translate( + 'vitality.appDetailsPage.auditHistory.table.columns.duration', + )} + + {translate( + 'vitality.appDetailsPage.auditHistory.table.columns.audits', + )} +
+
+ + {translate( + `vitality.appDetailsPage.auditHistory.statuses.${run.runStatus}`, + ) || run.runStatus} + + {run.errorMessage && ( +

+ {run.errorMessage} +

+ )} +
+
+
+ {run.analysisTypes?.map((type, idx) => ( + + {type} + + ))} +
+
+ {formatDate(run.triggeredAt)} + + {getDuration(run.triggeredAt, run.completedAt) || '-'} + + {run.audits?.length || 0} +
+
+
+ ); +}; + +export default VitalityAuditRunHistory; diff --git a/v6y-apps/front/src/features/app-details/components/audit-runs/VitalityAuditRunHistoryView.tsx b/v6y-apps/front/src/features/app-details/components/audit-runs/VitalityAuditRunHistoryView.tsx new file mode 100644 index 00000000..cc223667 --- /dev/null +++ b/v6y-apps/front/src/features/app-details/components/audit-runs/VitalityAuditRunHistoryView.tsx @@ -0,0 +1,77 @@ +'use client'; + +import React from 'react'; + +import VitalityApiConfig from '../../../../commons/config/VitalityApiConfig'; +import { + buildClientQuery, + useClientQuery, +} from '../../../../infrastructure/adapters/api/useQueryAdapter'; +import GetApplicationAuditRuns from '../../api/getApplicationAuditRuns.ts'; +import VitalityAuditRunHistory from './VitalityAuditRunHistory.tsx'; + +interface VitalityAuditRunHistoryViewProps { + applicationId: number; +} + +interface AuditRunType { + _id: number; + appId: number; + branch: string | null; + runStatus: string; + analysisTypes: string[]; + triggeredAt: string; + completedAt: string | null; + errorMessage: string | null; + audits: Array<{ + _id: number; + type: string; + category: string; + }>; +} + +export const VitalityAuditRunHistoryView: React.FC = ({ + applicationId, +}) => { + const { isLoading, data, error } = useClientQuery<{ + getApplicationAuditRunsByParams: AuditRunType[]; + }>({ + queryCacheKey: ['getApplicationAuditRunsByParams', `${applicationId}`], + queryBuilder: async () => + buildClientQuery({ + queryBaseUrl: VitalityApiConfig.VITALITY_BFF_URL as string, + query: GetApplicationAuditRuns, + variables: { + _id: applicationId, + }, + }), + }); + + React.useEffect(() => { + console.log('[VitalityAuditRunHistoryView]', { + applicationId, + isLoading, + dataLength: data?.getApplicationAuditRunsByParams?.length, + data, + error, + }); + }, [applicationId, isLoading, data, error]); + + if (error) { + console.error('[VitalityAuditRunHistoryView] Error fetching audit runs:', error); + return ( +
+ Failed to load audit history +
+ ); + } + + return ( + + ); +}; + +export default VitalityAuditRunHistoryView; diff --git a/v6y-apps/front/src/features/app-details/components/audit-runs/index.ts b/v6y-apps/front/src/features/app-details/components/audit-runs/index.ts new file mode 100644 index 00000000..d0978b0b --- /dev/null +++ b/v6y-apps/front/src/features/app-details/components/audit-runs/index.ts @@ -0,0 +1,2 @@ +export { default as VitalityAuditRunHistory } from './VitalityAuditRunHistory.tsx'; +export { default as VitalityAuditRunHistoryView } from './VitalityAuditRunHistoryView.tsx'; diff --git a/v6y-libs/core-logic/src/database/AuditRunProvider.ts b/v6y-libs/core-logic/src/database/AuditRunProvider.ts index e77912f6..ed08c3f7 100644 --- a/v6y-libs/core-logic/src/database/AuditRunProvider.ts +++ b/v6y-libs/core-logic/src/database/AuditRunProvider.ts @@ -168,7 +168,22 @@ const getAuditRunsByApplicationId = async (appId: number, limit?: number, offset AppLogger.info( '[AuditRunProvider - getAuditRunsByApplicationId] count: ' + auditRuns?.length, ); - return auditRuns.map((item) => ({ ...item, _id: item.id })); + + const mapped = auditRuns.map((run) => ({ + ...run, + _id: run.id, + audits: run.audits.map((audit) => ({ + ...audit, + _id: audit.id, + })), + })); + + AppLogger.info( + '[AuditRunProvider - getAuditRunsByApplicationId] mapped first: ' + + JSON.stringify(mapped[0]), + ); + + return mapped; } catch (error) { AppLogger.error('[AuditRunProvider - getAuditRunsByApplicationId] error: ', error); return []; @@ -230,6 +245,7 @@ const AuditRunProvider = { getAuditRunWithAudits, getAuditRunById, getAuditRunsByApplicationId, + getAuditRunsByAppId: getAuditRunsByApplicationId, getLatestAuditRun, getAuditRunsCountByApplicationId, deleteAuditRun, From 686b97eab0128a19fae0f03149971f186e0dce19 Mon Sep 17 00:00:00 2001 From: "theo.gillet" Date: Tue, 26 May 2026 17:33:32 +0200 Subject: [PATCH 7/7] [FEAT]: Display audit history only on app details overview tab --- .../application/ApplicationQueries.ts | 45 +++++++++++++++++++ .../application/ApplicationQueriesType.ts | 1 + .../app-details/api/getAllAuditRuns.ts | 23 ++++++++++ .../components/VitalityAppDetailsView.tsx | 27 ++++++----- .../VitalityAuditRunHistoryView.tsx | 36 ++++++++------- .../src/database/AuditRunProvider.ts | 26 +++++++++++ 6 files changed, 131 insertions(+), 27 deletions(-) create mode 100644 v6y-apps/front/src/features/app-details/api/getAllAuditRuns.ts diff --git a/v6y-apps/bff/src/resolvers/application/ApplicationQueries.ts b/v6y-apps/bff/src/resolvers/application/ApplicationQueries.ts index 5cc6431d..4fd78b18 100644 --- a/v6y-apps/bff/src/resolvers/application/ApplicationQueries.ts +++ b/v6y-apps/bff/src/resolvers/application/ApplicationQueries.ts @@ -130,6 +130,50 @@ const getApplicationAuditRunsByParams = async ( } }; +/** + * Get all audit runs across all applications (respecting user authorization) + * @param _ + * @param args + * @param user + */ +const getAllAuditRuns = async ( + _: unknown, + args: { limit?: number; offset?: number }, + { user }: { user: AccountType }, +) => { + try { + const { limit, offset } = args || {}; + + AppLogger.info( + `[ApplicationQueries - getAllAuditRuns] Received args: limit=${limit}, offset=${offset}`, + ); + + // Get all audit runs + const allAuditRuns = await AuditRunProvider.getAllAuditRuns(limit, offset); + + // If user is SUPERADMIN or ADMIN, return all audit runs + if (user.role === 'ADMIN' || user.role === 'SUPERADMIN') { + AppLogger.info( + `[ApplicationQueries - getAllAuditRuns] Returning ${allAuditRuns?.length} audit runs for ${user.role}`, + ); + return allAuditRuns || []; + } + + // Otherwise, filter to only include audit runs from apps the user has access to + const userApplicationsIds = user.applications || []; + const filteredRuns = allAuditRuns.filter((run) => userApplicationsIds.includes(run.appId)); + + AppLogger.info( + `[ApplicationQueries - getAllAuditRuns] Returning ${filteredRuns?.length} audit runs for user with access to ${userApplicationsIds.length} apps`, + ); + + return filteredRuns || []; + } catch (error) { + AppLogger.error(`[ApplicationQueries - getAllAuditRuns] error: ${error}`); + return []; + } +}; + /** * Get application details evolutions by params * @param _ @@ -534,6 +578,7 @@ const ApplicationQueries = { getApplicationDetailsInfoByParams, getApplicationDetailsAuditReportsByParams, getApplicationAuditRunsByParams, + getAllAuditRuns, getApplicationDetailsEvolutionsByParams, getApplicationDetailsDependenciesByParams, getApplicationDetailsKeywordsByParams, diff --git a/v6y-apps/bff/src/types/application/ApplicationQueriesType.ts b/v6y-apps/bff/src/types/application/ApplicationQueriesType.ts index 20a79ffc..1a1fd340 100644 --- a/v6y-apps/bff/src/types/application/ApplicationQueriesType.ts +++ b/v6y-apps/bff/src/types/application/ApplicationQueriesType.ts @@ -13,6 +13,7 @@ const ApplicationQueriesType = ` getApplicationAuditHistoryCountByParams(_id: Int!): Int getApplicationLatestAuditRunByParams(_id: Int!): AuditRunType getAuditRunDetailsByParams(_id: Int!): AuditRunType + getAllAuditRuns(limit: Int, offset: Int): [AuditRunType] } `; diff --git a/v6y-apps/front/src/features/app-details/api/getAllAuditRuns.ts b/v6y-apps/front/src/features/app-details/api/getAllAuditRuns.ts new file mode 100644 index 00000000..18ed95a5 --- /dev/null +++ b/v6y-apps/front/src/features/app-details/api/getAllAuditRuns.ts @@ -0,0 +1,23 @@ +import { gql } from 'graphql-request'; + +const GetAllAuditRuns = gql` + query getAllAuditRuns { + getAllAuditRuns { + _id + appId + branch + runStatus + analysisTypes + triggeredAt + completedAt + errorMessage + audits { + _id + type + category + } + } + } +`; + +export default GetAllAuditRuns; diff --git a/v6y-apps/front/src/features/app-details/components/VitalityAppDetailsView.tsx b/v6y-apps/front/src/features/app-details/components/VitalityAppDetailsView.tsx index b78ca468..cc973679 100644 --- a/v6y-apps/front/src/features/app-details/components/VitalityAppDetailsView.tsx +++ b/v6y-apps/front/src/features/app-details/components/VitalityAppDetailsView.tsx @@ -147,11 +147,21 @@ const VitalityAppDetailsView = () => { switch (activeTab) { case 'overview': return ( - +
+ +
+

+ {translate('vitality.appDetailsPage.auditHistory.title')} +

+ +
+
); case 'performance': return ( @@ -330,13 +340,6 @@ const VitalityAppDetailsView = () => { {renderTabContent()} - -
-

- {translate('vitality.appDetailsPage.auditHistory.title')} -

- -
diff --git a/v6y-apps/front/src/features/app-details/components/audit-runs/VitalityAuditRunHistoryView.tsx b/v6y-apps/front/src/features/app-details/components/audit-runs/VitalityAuditRunHistoryView.tsx index cc223667..91b72097 100644 --- a/v6y-apps/front/src/features/app-details/components/audit-runs/VitalityAuditRunHistoryView.tsx +++ b/v6y-apps/front/src/features/app-details/components/audit-runs/VitalityAuditRunHistoryView.tsx @@ -7,11 +7,12 @@ import { buildClientQuery, useClientQuery, } from '../../../../infrastructure/adapters/api/useQueryAdapter'; +import GetAllAuditRuns from '../../api/getAllAuditRuns.ts'; import GetApplicationAuditRuns from '../../api/getApplicationAuditRuns.ts'; import VitalityAuditRunHistory from './VitalityAuditRunHistory.tsx'; interface VitalityAuditRunHistoryViewProps { - applicationId: number; + applicationId?: number; } interface AuditRunType { @@ -33,17 +34,21 @@ interface AuditRunType { export const VitalityAuditRunHistoryView: React.FC = ({ applicationId, }) => { + const isAppSpecific = applicationId !== undefined; + const cacheKey = isAppSpecific + ? ['getApplicationAuditRunsByParams', `${applicationId}`] + : ['getAllAuditRuns']; + const { isLoading, data, error } = useClientQuery<{ - getApplicationAuditRunsByParams: AuditRunType[]; + getApplicationAuditRunsByParams?: AuditRunType[]; + getAllAuditRuns?: AuditRunType[]; }>({ - queryCacheKey: ['getApplicationAuditRunsByParams', `${applicationId}`], + queryCacheKey: cacheKey, queryBuilder: async () => buildClientQuery({ queryBaseUrl: VitalityApiConfig.VITALITY_BFF_URL as string, - query: GetApplicationAuditRuns, - variables: { - _id: applicationId, - }, + query: isAppSpecific ? GetApplicationAuditRuns : GetAllAuditRuns, + variables: isAppSpecific ? { _id: applicationId } : {}, }), }); @@ -51,11 +56,13 @@ export const VitalityAuditRunHistoryView: React.FC - ); + const auditRuns = isAppSpecific + ? data?.getApplicationAuditRunsByParams || [] + : data?.getAllAuditRuns || []; + + return ; }; export default VitalityAuditRunHistoryView; diff --git a/v6y-libs/core-logic/src/database/AuditRunProvider.ts b/v6y-libs/core-logic/src/database/AuditRunProvider.ts index ed08c3f7..70cec0de 100644 --- a/v6y-libs/core-logic/src/database/AuditRunProvider.ts +++ b/v6y-libs/core-logic/src/database/AuditRunProvider.ts @@ -238,6 +238,31 @@ const deleteAuditRun = async (auditRunId: number) => { } }; +const getAllAuditRuns = async (limit?: number, offset?: number) => { + try { + AppLogger.info( + '[AuditRunProvider - getAllAuditRuns] limit: ' + limit + ', offset: ' + offset, + ); + + const auditRuns = await getPrismaClient().auditRun.findMany({ + take: limit, + skip: offset, + orderBy: { triggeredAt: 'desc' }, + include: { audits: true }, + }); + + AppLogger.info('[AuditRunProvider - getAllAuditRuns] count: ' + auditRuns?.length); + + const mapped = auditRuns.map((run) => ({ ...run, _id: run.id })); + + AppLogger.info('[AuditRunProvider - getAllAuditRuns] mapped first: ' + mapped[0]?.appId); + return mapped || []; + } catch (error) { + AppLogger.error('[AuditRunProvider - getAllAuditRuns] error: ', error); + return []; + } +}; + const AuditRunProvider = { createAuditRun, updateAuditRunStatus, @@ -249,6 +274,7 @@ const AuditRunProvider = { getLatestAuditRun, getAuditRunsCountByApplicationId, deleteAuditRun, + getAllAuditRuns, }; export default AuditRunProvider;