From f5c10ffca20a6c381934762df4dc39eba486cbf3 Mon Sep 17 00:00:00 2001 From: "theo.gillet" Date: Thu, 7 May 2026 09:28:41 +0200 Subject: [PATCH 1/6] [FEAT]: added branch filter on audit worker --- pnpm-lock.yaml | 12 ++++++------ v6y-apps/bfb-dynamic-auditor/package.json | 2 +- .../src/types/ecoindex.d.ts | 6 +++--- .../src/managers/ApplicationManager.ts | 19 ++++++++++++++++++- .../ApplicationCreateOrEditInput.ts | 5 ++++- .../bff/src/types/commons/RepositoryType.ts | 3 +++ .../front-bo/public/locales/en/common.json | 4 ++++ .../front-bo/public/locales/fr/common.json | 6 ++++++ .../src/__tests__/VitalityFormConfig-test.ts | 6 +++++- .../src/commons/config/VitalityFormConfig.tsx | 9 +++++++++ .../apis/getApplicationDetails.ts | 1 + .../src/database/ApplicationProvider.ts | 8 +++++++- .../core-logic/src/types/ApplicationType.ts | 1 + .../core-logic/src/types/RepositoryType.ts | 2 ++ 14 files changed, 70 insertions(+), 14 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 25cc7195..2cf0b7e0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -192,8 +192,8 @@ importers: specifier: '=4.1.0' version: 4.1.0 ecoindex: - specifier: '=1.0.7' - version: 1.0.7 + specifier: '=2.0.1' + version: 2.0.1 express: specifier: '=5.2.0' version: 5.2.0 @@ -6897,9 +6897,9 @@ packages: ecdsa-sig-formatter@1.0.11: resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} - ecoindex@1.0.7: - resolution: {integrity: sha512-0fJWWWuJ2T9ykpvZ0O6MJX/qMjZ/Z17s4tWvfEWxAuX8YuhVKZ2LXdrUu67cs2FFdHck2oeUP7Uf4NGefGdQfQ==} - engines: {node: '>=16.0.0 <18.20.0'} + ecoindex@2.0.1: + resolution: {integrity: sha512-62wNhERrLcrJ7ItG1BXCOEGZCWt2f9wHq1nXua73tQ/2PKV1vup5Kj2bQiqQxBzslmNYQ8H79CQOtyHbmiQqxA==} + engines: {node: '>=18.20.0'} ecoindex_reference@1.0.2: resolution: {integrity: sha512-IKse3tu6IQfhrpGsVuxmC1K4xlc1FCZtaNYjLUj+Yxm5wthuVyS7C/miVPqSN7UZaNViUO2013ri+WJJhZfCUA==} @@ -18368,7 +18368,7 @@ snapshots: dependencies: safe-buffer: 5.2.1 - ecoindex@1.0.7: + ecoindex@2.0.1: dependencies: ecoindex_reference: 1.0.2 diff --git a/v6y-apps/bfb-dynamic-auditor/package.json b/v6y-apps/bfb-dynamic-auditor/package.json index d6887057..d301d7f3 100644 --- a/v6y-apps/bfb-dynamic-auditor/package.json +++ b/v6y-apps/bfb-dynamic-auditor/package.json @@ -58,7 +58,7 @@ "cors": "=2.8.5", "cron": "=4.3.3", "date-fns": "=4.1.0", - "ecoindex": "=1.0.7", + "ecoindex": "=2.0.1", "express": "=5.2.0", "express-status-monitor": "=1.3.4", "fs-extra": "=11.2.0", diff --git a/v6y-apps/bfb-dynamic-auditor/src/types/ecoindex.d.ts b/v6y-apps/bfb-dynamic-auditor/src/types/ecoindex.d.ts index 7654f0a5..93c48ebd 100644 --- a/v6y-apps/bfb-dynamic-auditor/src/types/ecoindex.d.ts +++ b/v6y-apps/bfb-dynamic-auditor/src/types/ecoindex.d.ts @@ -1,9 +1,9 @@ declare module 'ecoindex' { export function computeEcoIndex(dom: number, req: number, size: number): number; + export function computeQuantile(quantiles: number[], value: number): number; export function getEcoIndexGrade(ecoIndex: number): string | false; + export function getEcoIndexGradesList(): { value: number; grade: string }[]; + export function getQuantiles(): { dom: number[]; req: number[]; size: number[] }; export function computeGreenhouseGasesEmissionfromEcoIndex(ecoIndex: number): string; export function computeWaterConsumptionfromEcoIndex(ecoIndex: number): string; - export function computeQuantile(quantiles: number[], value: number): number; - export function getQuantiles(): { dom: number[]; req: number[]; size: number[] }; - export function getEcoIndexGradesList(): { grade: string; value: number }[]; } diff --git a/v6y-apps/bfb-main-analyzer/src/managers/ApplicationManager.ts b/v6y-apps/bfb-main-analyzer/src/managers/ApplicationManager.ts index 71410dce..2c4b5e11 100644 --- a/v6y-apps/bfb-main-analyzer/src/managers/ApplicationManager.ts +++ b/v6y-apps/bfb-main-analyzer/src/managers/ApplicationManager.ts @@ -47,11 +47,28 @@ const buildApplicationReports = async (application: ApplicationType) => { return false; } + const targetBranchName = + application.repo.defaultBranch || + repositoryDetails.default_branch || + repositoryBranches[0]?.name; + + const targetBranches = repositoryBranches.filter( + (branch) => branch?.name === targetBranchName, + ); + + const branchesToAudit = targetBranches.length ? targetBranches : [repositoryBranches[0]]; + + AppLogger.info( + '[ApplicationManager - buildApplicationReports] auditing branch: ', + branchesToAudit[0]?.name, + ); + await ApplicationProvider.editApplication({ ...application, repo: { ...application?.repo, allBranches: repositoryBranches.map((branch) => branch?.name), + defaultBranch: branchesToAudit[0]?.name, }, }); @@ -59,7 +76,7 @@ const buildApplicationReports = async (application: ApplicationType) => { await buildStaticReports({ application, - branches: repositoryBranches, + branches: branchesToAudit, }); AppLogger.info('[ApplicationManager - buildApplicationDetails] end of static analysis'); diff --git a/v6y-apps/bff/src/types/application/ApplicationCreateOrEditInput.ts b/v6y-apps/bff/src/types/application/ApplicationCreateOrEditInput.ts index b91fc8b4..c9a86da1 100644 --- a/v6y-apps/bff/src/types/application/ApplicationCreateOrEditInput.ts +++ b/v6y-apps/bff/src/types/application/ApplicationCreateOrEditInput.ts @@ -20,7 +20,10 @@ const ApplicationCreateOrEditInput = ` """ Application git repository url """ gitUrl: String! - + + """ Default branch to audit (leave empty to auto-detect from repository) """ + gitDefaultBranch: String + """ Application production url """ productionLink: String! diff --git a/v6y-apps/bff/src/types/commons/RepositoryType.ts b/v6y-apps/bff/src/types/commons/RepositoryType.ts index 9c1c83db..4f6018b6 100644 --- a/v6y-apps/bff/src/types/commons/RepositoryType.ts +++ b/v6y-apps/bff/src/types/commons/RepositoryType.ts @@ -11,6 +11,9 @@ const RepositoryType = ` """ Repository Organization """ organization: String + + """ Default branch to audit """ + defaultBranch: String """ Repository all related branches """ allBranches: [String] diff --git a/v6y-apps/front-bo/public/locales/en/common.json b/v6y-apps/front-bo/public/locales/en/common.json index 78372150..b2a86614 100644 --- a/v6y-apps/front-bo/public/locales/en/common.json +++ b/v6y-apps/front-bo/public/locales/en/common.json @@ -197,6 +197,10 @@ "placeholder": "Enter your application Git URL", "error": "Application Git URL is a mandatory field!" }, + "app-git-default-branch": { + "label": "Default branch to audit (leave empty to auto-detect from repository)", + "placeholder": "e.g. main, master, develop" + }, "app-links": { "label": "Application links" }, diff --git a/v6y-apps/front-bo/public/locales/fr/common.json b/v6y-apps/front-bo/public/locales/fr/common.json index 7f8a279d..ddb83e47 100644 --- a/v6y-apps/front-bo/public/locales/fr/common.json +++ b/v6y-apps/front-bo/public/locales/fr/common.json @@ -109,6 +109,7 @@ "create": "Create", "edit": "Edit", "show": "Show" + }, "buttons": { "create": "Create", @@ -196,6 +197,10 @@ "placeholder": "Enter your application Git URL", "error": "Application Git URL is a mandatory field!" }, + "app-git-default-branch": { + "label": "Branche par défaut à auditer (laisser vide pour détecter automatiquement depuis le dépôt)", + "placeholder": "ex: main, master, develop" + }, "app-links": { "label": "Application links" }, @@ -459,3 +464,4 @@ "title": "Authentication" } } + diff --git a/v6y-apps/front-bo/src/__tests__/VitalityFormConfig-test.ts b/v6y-apps/front-bo/src/__tests__/VitalityFormConfig-test.ts index 9dfd6f34..6168bc5b 100644 --- a/v6y-apps/front-bo/src/__tests__/VitalityFormConfig-test.ts +++ b/v6y-apps/front-bo/src/__tests__/VitalityFormConfig-test.ts @@ -29,10 +29,11 @@ describe('VitalityFormConfig - Form Items', () => { it('should generate application git repository form items correctly', () => { const result = applicationGitRepositoryFormItems(mockTranslate); - expect(result).toHaveLength(3); + expect(result).toHaveLength(4); expect(result[0].id).toBe('app-git-organization'); expect(result[1].id).toBe('app-git-web-url'); expect(result[2].id).toBe('app-git-url'); + expect(result[3].id).toBe('app-git-default-branch'); }); it('should generate required application links form items correctly', () => { @@ -109,6 +110,7 @@ describe('VitalityFormConfig - Form Items', () => { 'app-git-organization': 'TestOrg', 'app-git-web-url': 'https://testrepo.com', 'app-git-url': 'https://git.testrepo.com', + 'app-git-default-branch': undefined, 'app-production-link-1': 'https://testapp.com', 'app-production-link-2': 'https://testapp2.com', 'app-production-link-3': 'https://testapp3.com', @@ -156,6 +158,7 @@ describe('VitalityFormConfig - Form Items', () => { gitOrganization: 'TestOrg', gitWebUrl: 'https://testrepo.com', gitUrl: 'https://git.testrepo.com', + gitDefaultBranch: undefined, productionLink: 'https://testapp.com', additionalProductionLinks: ['https://testapp2.com', 'https://testapp3.com'], sonarqubeLink: 'https://sonarqube.example.com/dashboard?id=testapp', @@ -208,6 +211,7 @@ describe('VitalityFormConfig - Form Items', () => { 'app-git-organization': undefined, 'app-git-web-url': undefined, 'app-git-url': 'https://git.partial.com', + 'app-git-default-branch': undefined, 'app-production-link-1': undefined, 'app-production-link-2': undefined, 'app-production-link-3': undefined, diff --git a/v6y-apps/front-bo/src/commons/config/VitalityFormConfig.tsx b/v6y-apps/front-bo/src/commons/config/VitalityFormConfig.tsx index e1293486..9708a0e2 100644 --- a/v6y-apps/front-bo/src/commons/config/VitalityFormConfig.tsx +++ b/v6y-apps/front-bo/src/commons/config/VitalityFormConfig.tsx @@ -99,6 +99,13 @@ export const applicationGitRepositoryFormItems = (translate: TranslateType) => [ }, ], }, + { + id: 'app-git-default-branch', + name: 'app-git-default-branch', + label: translate('v6y-applications.fields.app-git-default-branch.label'), + placeholder: translate('v6y-applications.fields.app-git-default-branch.placeholder'), + rules: [], + }, ]; export const applicationRequiredLinksFormItems = (translate: TranslateType) => [ @@ -250,6 +257,7 @@ export const applicationCreateOrEditFormInAdapter = (params: ApplicationType) => 'app-git-organization': params?.['repo']?.organization, 'app-git-web-url': params?.['repo']?.webUrl, 'app-git-url': params?.['repo']?.gitUrl, + 'app-git-default-branch': params?.['repo']?.defaultBranch, 'app-contact-email': params?.['contactMail'], 'app-production-link-1': params?.['links']?.find?.( (item) => item.label === 'Application production url', @@ -289,6 +297,7 @@ export const applicationCreateOrEditFormOutputAdapter = (data: unknown): Variabl gitOrganization: params?.['app-git-organization'], gitWebUrl: params?.['app-git-web-url'], gitUrl: params?.['app-git-url'], + gitDefaultBranch: params?.['app-git-default-branch'], name: params?.['app-name'], contactMail: params?.['app-contact-email'], productionLink: params?.['app-production-link-1'], diff --git a/v6y-apps/front-bo/src/features/v6y-applications/apis/getApplicationDetails.ts b/v6y-apps/front-bo/src/features/v6y-applications/apis/getApplicationDetails.ts index 36249137..7aa6b980 100644 --- a/v6y-apps/front-bo/src/features/v6y-applications/apis/getApplicationDetails.ts +++ b/v6y-apps/front-bo/src/features/v6y-applications/apis/getApplicationDetails.ts @@ -17,6 +17,7 @@ const GetApplicationDetails = gql` organization webUrl gitUrl + defaultBranch } configuration { dataDog { diff --git a/v6y-libs/core-logic/src/database/ApplicationProvider.ts b/v6y-libs/core-logic/src/database/ApplicationProvider.ts index f1a62cb0..c26973bc 100644 --- a/v6y-libs/core-logic/src/database/ApplicationProvider.ts +++ b/v6y-libs/core-logic/src/database/ApplicationProvider.ts @@ -19,6 +19,7 @@ const formatApplicationInput = (application: ApplicationInputType): ApplicationT gitOrganization, gitUrl, gitWebUrl, + gitDefaultBranch, productionLink, sonarqubeLink, sonarqubeToken, @@ -38,7 +39,12 @@ const formatApplicationInput = (application: ApplicationInputType): ApplicationT acronym, description, contactMail, - repo: { webUrl: gitWebUrl, gitUrl, organization: gitOrganization }, + repo: { + webUrl: gitWebUrl, + gitUrl, + organization: gitOrganization, + ...(gitDefaultBranch ? { defaultBranch: gitDefaultBranch } : {}), + }, links: [ { label: 'Application production url', value: productionLink, description: '' }, ...(additionalProductionLinks?.map((link, index) => ({ diff --git a/v6y-libs/core-logic/src/types/ApplicationType.ts b/v6y-libs/core-logic/src/types/ApplicationType.ts index 03d6c46a..34f477e1 100644 --- a/v6y-libs/core-logic/src/types/ApplicationType.ts +++ b/v6y-libs/core-logic/src/types/ApplicationType.ts @@ -26,6 +26,7 @@ export interface ApplicationInputType { gitOrganization?: string; gitUrl?: string; gitWebUrl?: string; + gitDefaultBranch?: string; productionLink?: string; sonarqubeLink?: string; sonarqubeToken?: string; diff --git a/v6y-libs/core-logic/src/types/RepositoryType.ts b/v6y-libs/core-logic/src/types/RepositoryType.ts index 349e0e9d..9ac09ab2 100644 --- a/v6y-libs/core-logic/src/types/RepositoryType.ts +++ b/v6y-libs/core-logic/src/types/RepositoryType.ts @@ -53,7 +53,9 @@ export interface RepositoryType { webUrl?: string; gitUrl?: string; organization?: string; + defaultBranch?: string; allBranches?: string[]; + default_branch?: string; id?: string; archived?: string; empty_repo?: string; From f84a3e7b78981bb4f228ca15d70b30186e241af7 Mon Sep 17 00:00:00 2001 From: "theo.gillet" Date: Thu, 7 May 2026 16:12:59 +0200 Subject: [PATCH 2/6] [FEAT] add multi-branch audit support and triggerApplicationAnalysis mutation - Replace gitDefaultBranch with gitBranchesToAudit (string[]) in core-logic types and BFF schema - Preserve allBranches on form save by merging existing repo data in editFormApplication - Add deleteAuditsByAppId and deleteDependenciesByAppId to AuditProvider/DependencyProvider - Export buildApplicationReports from ApplicationManager - Add POST /trigger endpoint to bfb-main-analyzer to run on-demand analysis - Add triggerApplicationAnalysis GraphQL mutation in BFF --- v6y-apps/bfb-main-analyzer/src/app.ts | 40 ++++++++++++++++- .../src/managers/ApplicationManager.ts | 32 ++++++++------ .../application/ApplicationMutations.ts | 44 +++++++++++++++++++ .../ApplicationCreateOrEditInput.ts | 4 +- .../application/ApplicationMutationsType.ts | 7 +++ .../bff/src/types/commons/RepositoryType.ts | 4 +- .../src/database/ApplicationProvider.ts | 21 ++++++--- .../core-logic/src/database/AuditProvider.ts | 18 ++++++++ .../src/database/DependencyProvider.ts | 18 ++++++++ .../core-logic/src/types/ApplicationType.ts | 2 +- .../core-logic/src/types/RepositoryType.ts | 2 +- 11 files changed, 167 insertions(+), 25 deletions(-) diff --git a/v6y-apps/bfb-main-analyzer/src/app.ts b/v6y-apps/bfb-main-analyzer/src/app.ts index 7cbef461..20cd30f8 100644 --- a/v6y-apps/bfb-main-analyzer/src/app.ts +++ b/v6y-apps/bfb-main-analyzer/src/app.ts @@ -4,9 +4,17 @@ 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, + AuditProvider, + CorsOptions, + DataBaseManager, + DependencyProvider, +} from '@v6y/core-logic'; import ServerConfig from './config/ServerConfig.ts'; +import ApplicationManager from './managers/ApplicationManager.ts'; /** * Creates and configures the Express application @@ -48,6 +56,36 @@ export function createApp(): ExpressApp { // *********************************************** Handle Endpoints *********************************************** + // Trigger analysis for a single application + app.post('/{*any}trigger', async (request, response) => { + const applicationId = parseInt(request.body?.applicationId, 10); + AppLogger.info(`[trigger] applicationId: ${applicationId}`); + + if (!applicationId) { + response.status(400).json({ success: false, message: 'applicationId is required' }); + return; + } + + // Respond immediately; analysis runs in background + response.json({ success: true, applicationId }); + + try { + await DataBaseManager.connect(); + await AuditProvider.deleteAuditsByAppId(applicationId); + await DependencyProvider.deleteDependenciesByAppId(applicationId); + + const application = await ApplicationProvider.getApplicationDetailsInfoByParams({ + _id: applicationId, + }); + + if (application) { + await ApplicationManager.buildApplicationReports(application); + } + } catch (error) { + AppLogger.error(`[trigger] error: ${String(error)}`); + } + }); + // default response (unknown routes) app.get('/{*any}', (request, response) => { AppLogger.debug(`[*] KO: la route demandé ${request.url} n'existe pas`); diff --git a/v6y-apps/bfb-main-analyzer/src/managers/ApplicationManager.ts b/v6y-apps/bfb-main-analyzer/src/managers/ApplicationManager.ts index 2c4b5e11..4462a501 100644 --- a/v6y-apps/bfb-main-analyzer/src/managers/ApplicationManager.ts +++ b/v6y-apps/bfb-main-analyzer/src/managers/ApplicationManager.ts @@ -47,20 +47,26 @@ const buildApplicationReports = async (application: ApplicationType) => { return false; } - const targetBranchName = - application.repo.defaultBranch || - repositoryDetails.default_branch || - repositoryBranches[0]?.name; - - const targetBranches = repositoryBranches.filter( - (branch) => branch?.name === targetBranchName, - ); - - const branchesToAudit = targetBranches.length ? targetBranches : [repositoryBranches[0]]; + const userBranchesToAudit = application.repo.branchesToAudit; + + let branchesToAudit; + if (userBranchesToAudit?.length) { + const matched = repositoryBranches.filter((branch) => + userBranchesToAudit.includes(branch?.name), + ); + branchesToAudit = matched.length ? matched : [repositoryBranches[0]]; + } else { + const defaultBranchName = + repositoryDetails.default_branch || repositoryBranches[0]?.name; + const defaultBranch = repositoryBranches.find( + (branch) => branch?.name === defaultBranchName, + ); + branchesToAudit = defaultBranch ? [defaultBranch] : [repositoryBranches[0]]; + } AppLogger.info( - '[ApplicationManager - buildApplicationReports] auditing branch: ', - branchesToAudit[0]?.name, + '[ApplicationManager - buildApplicationReports] auditing branches: ', + branchesToAudit.map((b) => b?.name).join(', '), ); await ApplicationProvider.editApplication({ @@ -68,7 +74,6 @@ const buildApplicationReports = async (application: ApplicationType) => { repo: { ...application?.repo, allBranches: repositoryBranches.map((branch) => branch?.name), - defaultBranch: branchesToAudit[0]?.name, }, }); @@ -126,6 +131,7 @@ const buildApplicationList = async () => { }; const ApplicationManager = { + buildApplicationReports, buildApplicationList, }; diff --git a/v6y-apps/bff/src/resolvers/application/ApplicationMutations.ts b/v6y-apps/bff/src/resolvers/application/ApplicationMutations.ts index 28be3d1b..1a5612d8 100644 --- a/v6y-apps/bff/src/resolvers/application/ApplicationMutations.ts +++ b/v6y-apps/bff/src/resolvers/application/ApplicationMutations.ts @@ -23,6 +23,7 @@ const createOrEditApplication = async ( gitOrganization, gitUrl, gitWebUrl, + gitBranchesToAudit, productionLink, additionalProductionLinks, dataDogApiKey, @@ -46,6 +47,9 @@ const createOrEditApplication = async ( AppLogger.info( `[AppMutations - createOrEditApplication] gitOrganization : ${gitOrganization}`, ); + AppLogger.info( + `[AppMutations - createOrEditApplication] gitBranchesToAudit : ${gitBranchesToAudit}`, + ); AppLogger.info( `[AppMutations - createOrEditApplication] dataDogApiKey : ${dataDogApiKey ? '"********"}`,' : 'null'}`, ); @@ -83,6 +87,7 @@ const createOrEditApplication = async ( gitOrganization, gitUrl, gitWebUrl, + gitBranchesToAudit, productionLink, contactMail, sonarqubeLink, @@ -111,6 +116,7 @@ const createOrEditApplication = async ( gitOrganization, gitUrl, gitWebUrl, + gitBranchesToAudit, productionLink, contactMail, sonarqubeLink, @@ -166,9 +172,47 @@ const deleteApplication = async (_: unknown, params: { input: SearchQueryType }) } }; +/** + * Trigger analysis for a single application on demand. + */ +const triggerApplicationAnalysis = async (_: unknown, params: { applicationId: number }) => { + try { + const { applicationId } = params || {}; + AppLogger.info( + `[AppMutations - triggerApplicationAnalysis] applicationId: ${applicationId}`, + ); + + if (!applicationId) { + return { success: false, message: 'applicationId is required' }; + } + + const mainAnalyzerPort = process.env.V6Y_MAIN_API_PORT || '4002'; + const mainAnalyzerPath = process.env.V6Y_MAIN_API_PATH || '/api/main'; + const triggerUrl = `http://localhost:${mainAnalyzerPort}${mainAnalyzerPath}trigger`; + + AppLogger.info(`[AppMutations - triggerApplicationAnalysis] calling: ${triggerUrl}`); + + const response = await fetch(triggerUrl, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ applicationId }), + }); + + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`); + } + + return { success: true, applicationId }; + } catch (error) { + AppLogger.info(`[AppMutations - triggerApplicationAnalysis] error: ${error}`); + return { success: false, message: String(error) }; + } +}; + const ApplicationMutations = { createOrEditApplication, deleteApplication, + triggerApplicationAnalysis, }; export default ApplicationMutations; diff --git a/v6y-apps/bff/src/types/application/ApplicationCreateOrEditInput.ts b/v6y-apps/bff/src/types/application/ApplicationCreateOrEditInput.ts index c9a86da1..a7682a95 100644 --- a/v6y-apps/bff/src/types/application/ApplicationCreateOrEditInput.ts +++ b/v6y-apps/bff/src/types/application/ApplicationCreateOrEditInput.ts @@ -21,8 +21,8 @@ const ApplicationCreateOrEditInput = ` """ Application git repository url """ gitUrl: String! - """ Default branch to audit (leave empty to auto-detect from repository) """ - gitDefaultBranch: String + """ Branches to audit (leave empty to auto-detect from repository) """ + gitBranchesToAudit: [String] """ Application production url """ productionLink: String! diff --git a/v6y-apps/bff/src/types/application/ApplicationMutationsType.ts b/v6y-apps/bff/src/types/application/ApplicationMutationsType.ts index d608a82c..c5f6b983 100644 --- a/v6y-apps/bff/src/types/application/ApplicationMutationsType.ts +++ b/v6y-apps/bff/src/types/application/ApplicationMutationsType.ts @@ -1,7 +1,14 @@ const ApplicationMutationsType = ` + type ApplicationAnalysisTriggerOutput { + success: Boolean! + applicationId: Int + message: String + } + type Mutation { createOrEditApplication(applicationInput: ApplicationCreateOrEditInput!): ApplicationType deleteApplication(input: ApplicationDeleteInput!): ApplicationDeleteOutput + triggerApplicationAnalysis(applicationId: Int!): ApplicationAnalysisTriggerOutput } `; diff --git a/v6y-apps/bff/src/types/commons/RepositoryType.ts b/v6y-apps/bff/src/types/commons/RepositoryType.ts index 4f6018b6..a97da9f8 100644 --- a/v6y-apps/bff/src/types/commons/RepositoryType.ts +++ b/v6y-apps/bff/src/types/commons/RepositoryType.ts @@ -12,8 +12,8 @@ const RepositoryType = ` """ Repository Organization """ organization: String - """ Default branch to audit """ - defaultBranch: String + """ Branches to audit (user-selected) """ + branchesToAudit: [String] """ Repository all related branches """ allBranches: [String] diff --git a/v6y-libs/core-logic/src/database/ApplicationProvider.ts b/v6y-libs/core-logic/src/database/ApplicationProvider.ts index c26973bc..fccb1ca7 100644 --- a/v6y-libs/core-logic/src/database/ApplicationProvider.ts +++ b/v6y-libs/core-logic/src/database/ApplicationProvider.ts @@ -19,7 +19,7 @@ const formatApplicationInput = (application: ApplicationInputType): ApplicationT gitOrganization, gitUrl, gitWebUrl, - gitDefaultBranch, + gitBranchesToAudit, productionLink, sonarqubeLink, sonarqubeToken, @@ -43,7 +43,7 @@ const formatApplicationInput = (application: ApplicationInputType): ApplicationT webUrl: gitWebUrl, gitUrl, organization: gitOrganization, - ...(gitDefaultBranch ? { defaultBranch: gitDefaultBranch } : {}), + ...(gitBranchesToAudit?.length ? { branchesToAudit: gitBranchesToAudit } : {}), }, links: [ { label: 'Application production url', value: productionLink, description: '' }, @@ -144,7 +144,10 @@ const createFormApplication = async (application: ApplicationInputType) => { const editFormApplication = async (application: ApplicationInputType) => { try { - if (!application?._id) return null; + if (!application?._id) { + return null; + } + const formApplication = formatApplicationInput(application); AppLogger.info( `[ApplicationProvider - editFormApplication] formApplication: ${formApplication?._id}`, @@ -154,11 +157,19 @@ const editFormApplication = async (application: ApplicationInputType) => { return null; } - // Merge configuration with existing DB value so write-only fields (e.g. sonarqube token) - // are not erased when the form is saved without re-entering them. const existing = await getPrismaClient().application.findUnique({ where: { id: application._id }, }); + + // Preserve analyzer-managed repo fields (e.g. allBranches) not controlled by form input. + if (existing?.repo) { + formApplication.repo = { + ...(existing.repo as object), + ...formApplication.repo, + }; + } + + // Preserve write-only config values (e.g. sonarqube token) when not re-submitted from the form. if (existing?.configuration) { formApplication.configuration = { ...(existing.configuration as object), diff --git a/v6y-libs/core-logic/src/database/AuditProvider.ts b/v6y-libs/core-logic/src/database/AuditProvider.ts index 1c10b879..57c7610e 100644 --- a/v6y-libs/core-logic/src/database/AuditProvider.ts +++ b/v6y-libs/core-logic/src/database/AuditProvider.ts @@ -119,6 +119,23 @@ const deleteAudit = async ({ _id }: AuditType) => { } }; +/** + * Deletes all Audit entries for a given application. + * @param appId + */ +const deleteAuditsByAppId = async (appId: number) => { + try { + AppLogger.info(`[AuditProvider - deleteAuditsByAppId] appId: ${appId}`); + await getPrismaClient().audit.deleteMany({ + where: { appId }, + }); + return true; + } catch (error) { + AppLogger.info(`[AuditProvider - deleteAuditsByAppId] error: ${error}`); + return false; + } +}; + const deleteAuditList = async () => { try { await getPrismaClient().audit.deleteMany(); @@ -147,6 +164,7 @@ const AuditProvider = { insertAuditList, editAudit, deleteAudit, + deleteAuditsByAppId, deleteAuditList, getAuditListByPageAndParams, }; diff --git a/v6y-libs/core-logic/src/database/DependencyProvider.ts b/v6y-libs/core-logic/src/database/DependencyProvider.ts index b813908a..6af7f283 100644 --- a/v6y-libs/core-logic/src/database/DependencyProvider.ts +++ b/v6y-libs/core-logic/src/database/DependencyProvider.ts @@ -111,6 +111,23 @@ const deleteDependency = async ({ _id }: DependencyType) => { } }; +/** + * Delete all Dependencies for a given application. + * @param appId + */ +const deleteDependenciesByAppId = async (appId: number) => { + try { + AppLogger.info(`[DependencyProvider - deleteDependenciesByAppId] appId: ${appId}`); + await getPrismaClient().dependency.deleteMany({ + where: { appId }, + }); + return true; + } catch (error) { + AppLogger.info(`[DependencyProvider - deleteDependenciesByAppId] error: ${error}`); + return false; + } +}; + const deleteDependencyList = async () => { try { await getPrismaClient().dependency.deleteMany(); @@ -138,6 +155,7 @@ const DependencyProvider = { insertDependencyList, editDependency, deleteDependency, + deleteDependenciesByAppId, deleteDependencyList, getDependencyListByPageAndParams, }; diff --git a/v6y-libs/core-logic/src/types/ApplicationType.ts b/v6y-libs/core-logic/src/types/ApplicationType.ts index 34f477e1..a4207553 100644 --- a/v6y-libs/core-logic/src/types/ApplicationType.ts +++ b/v6y-libs/core-logic/src/types/ApplicationType.ts @@ -26,7 +26,7 @@ export interface ApplicationInputType { gitOrganization?: string; gitUrl?: string; gitWebUrl?: string; - gitDefaultBranch?: string; + gitBranchesToAudit?: string[]; productionLink?: string; sonarqubeLink?: string; sonarqubeToken?: string; diff --git a/v6y-libs/core-logic/src/types/RepositoryType.ts b/v6y-libs/core-logic/src/types/RepositoryType.ts index 9ac09ab2..55eab1c0 100644 --- a/v6y-libs/core-logic/src/types/RepositoryType.ts +++ b/v6y-libs/core-logic/src/types/RepositoryType.ts @@ -53,7 +53,7 @@ export interface RepositoryType { webUrl?: string; gitUrl?: string; organization?: string; - defaultBranch?: string; + branchesToAudit?: string[]; allBranches?: string[]; default_branch?: string; id?: string; From 93314e3af4a3d0286c7796bb068e3c4b27f48be1 Mon Sep 17 00:00:00 2001 From: "theo.gillet" Date: Thu, 7 May 2026 16:13:16 +0200 Subject: [PATCH 3/6] [FEAT] add branch selector UI and Run Audit button in front and front-bo - Replace gitDefaultBranch input with multi-select branchesToAudit tags field in admin edit form - Populate branch select options from allBranches returned by the analyzer - Support function-based formItems in AdminEditWrapper to pass query data to form config - Show branchesToAudit (or 'All branches' fallback) in app details branch selector - Wire Run Audit button to triggerApplicationAnalysis mutation + invalidate React Query cache --- .../front-bo/public/locales/en/common.json | 6 +-- .../front-bo/public/locales/fr/common.json | 4 ++ .../src/__tests__/VitalityFormConfig-test.ts | 8 ++-- .../components/VitalityFormFieldSet.tsx | 6 ++- .../src/commons/config/VitalityFormConfig.tsx | 24 +++++++---- .../apis/getApplicationDetails.ts | 3 +- .../VitalityApplicationEditView.tsx | 9 +++- v6y-apps/front/public/locales/en/common.json | 3 +- v6y-apps/front/public/locales/fr/common.json | 3 +- .../api/getApplicationDetailsInfosByParams.ts | 1 + .../api/triggerApplicationAnalysis.ts | 13 ++++++ .../components/VitalityAppDetailsView.tsx | 43 +++++++++++++------ .../organisms/admin/AdminEditWrapper.tsx | 13 +++++- .../ui-kit/src/components/types/FormType.ts | 2 +- 14 files changed, 101 insertions(+), 37 deletions(-) create mode 100644 v6y-apps/front/src/features/app-details/api/triggerApplicationAnalysis.ts diff --git a/v6y-apps/front-bo/public/locales/en/common.json b/v6y-apps/front-bo/public/locales/en/common.json index b2a86614..c09709bf 100644 --- a/v6y-apps/front-bo/public/locales/en/common.json +++ b/v6y-apps/front-bo/public/locales/en/common.json @@ -197,9 +197,9 @@ "placeholder": "Enter your application Git URL", "error": "Application Git URL is a mandatory field!" }, - "app-git-default-branch": { - "label": "Default branch to audit (leave empty to auto-detect from repository)", - "placeholder": "e.g. main, master, develop" + "app-git-branches-to-audit": { + "label": "Branches to audit (type a branch name and press Enter; leave empty to auto-detect)", + "placeholder": "e.g. main, develop, release/1.0" }, "app-links": { "label": "Application links" diff --git a/v6y-apps/front-bo/public/locales/fr/common.json b/v6y-apps/front-bo/public/locales/fr/common.json index ddb83e47..1d42d64c 100644 --- a/v6y-apps/front-bo/public/locales/fr/common.json +++ b/v6y-apps/front-bo/public/locales/fr/common.json @@ -201,6 +201,10 @@ "label": "Branche par défaut à auditer (laisser vide pour détecter automatiquement depuis le dépôt)", "placeholder": "ex: main, master, develop" }, + "app-git-branches-to-audit": { + "label": "Branches à auditer (saisir un nom de branche et appuyer sur Entrée ; laisser vide pour détecter automatiquement)", + "placeholder": "ex: main, develop, release/1.0" + }, "app-links": { "label": "Application links" }, diff --git a/v6y-apps/front-bo/src/__tests__/VitalityFormConfig-test.ts b/v6y-apps/front-bo/src/__tests__/VitalityFormConfig-test.ts index 6168bc5b..6c0e2555 100644 --- a/v6y-apps/front-bo/src/__tests__/VitalityFormConfig-test.ts +++ b/v6y-apps/front-bo/src/__tests__/VitalityFormConfig-test.ts @@ -33,7 +33,7 @@ describe('VitalityFormConfig - Form Items', () => { expect(result[0].id).toBe('app-git-organization'); expect(result[1].id).toBe('app-git-web-url'); expect(result[2].id).toBe('app-git-url'); - expect(result[3].id).toBe('app-git-default-branch'); + expect(result[3].id).toBe('app-git-branches-to-audit'); }); it('should generate required application links form items correctly', () => { @@ -110,7 +110,7 @@ describe('VitalityFormConfig - Form Items', () => { 'app-git-organization': 'TestOrg', 'app-git-web-url': 'https://testrepo.com', 'app-git-url': 'https://git.testrepo.com', - 'app-git-default-branch': undefined, + 'app-git-branches-to-audit': undefined, 'app-production-link-1': 'https://testapp.com', 'app-production-link-2': 'https://testapp2.com', 'app-production-link-3': 'https://testapp3.com', @@ -158,7 +158,7 @@ describe('VitalityFormConfig - Form Items', () => { gitOrganization: 'TestOrg', gitWebUrl: 'https://testrepo.com', gitUrl: 'https://git.testrepo.com', - gitDefaultBranch: undefined, + gitBranchesToAudit: undefined, productionLink: 'https://testapp.com', additionalProductionLinks: ['https://testapp2.com', 'https://testapp3.com'], sonarqubeLink: 'https://sonarqube.example.com/dashboard?id=testapp', @@ -211,7 +211,7 @@ describe('VitalityFormConfig - Form Items', () => { 'app-git-organization': undefined, 'app-git-web-url': undefined, 'app-git-url': 'https://git.partial.com', - 'app-git-default-branch': undefined, + 'app-git-branches-to-audit': undefined, 'app-production-link-1': undefined, 'app-production-link-2': undefined, 'app-production-link-3': undefined, diff --git a/v6y-apps/front-bo/src/commons/components/VitalityFormFieldSet.tsx b/v6y-apps/front-bo/src/commons/components/VitalityFormFieldSet.tsx index d48e726a..a512f7af 100644 --- a/v6y-apps/front-bo/src/commons/components/VitalityFormFieldSet.tsx +++ b/v6y-apps/front-bo/src/commons/components/VitalityFormFieldSet.tsx @@ -24,7 +24,11 @@ const VitalityFormFieldSet = ({ groupTitle, items, selectOptions }: VitalityForm disabled={item.disabled || false} //defaultValue={item.defaultValue} placeholder={item.placeholder} - options={selectOptions} + options={ + ((item as Record).options as + | SelectOptionType[] + | undefined) || selectOptions + } mode={item.mode || undefined} /> )} diff --git a/v6y-apps/front-bo/src/commons/config/VitalityFormConfig.tsx b/v6y-apps/front-bo/src/commons/config/VitalityFormConfig.tsx index 9708a0e2..79db8afb 100644 --- a/v6y-apps/front-bo/src/commons/config/VitalityFormConfig.tsx +++ b/v6y-apps/front-bo/src/commons/config/VitalityFormConfig.tsx @@ -67,7 +67,10 @@ export const applicationInfosFormItems = (translate: TranslateType) => { ]; }; -export const applicationGitRepositoryFormItems = (translate: TranslateType) => [ +export const applicationGitRepositoryFormItems = ( + translate: TranslateType, + allBranches?: string[], +) => [ { id: 'app-git-organization', name: 'app-git-organization', @@ -100,10 +103,13 @@ export const applicationGitRepositoryFormItems = (translate: TranslateType) => [ ], }, { - id: 'app-git-default-branch', - name: 'app-git-default-branch', - label: translate('v6y-applications.fields.app-git-default-branch.label'), - placeholder: translate('v6y-applications.fields.app-git-default-branch.placeholder'), + id: 'app-git-branches-to-audit', + name: 'app-git-branches-to-audit', + type: 'select', + mode: 'tags', + label: translate('v6y-applications.fields.app-git-branches-to-audit.label'), + placeholder: translate('v6y-applications.fields.app-git-branches-to-audit.placeholder'), + options: allBranches?.map((branch) => ({ label: branch, value: branch })), rules: [], }, ]; @@ -219,7 +225,7 @@ export const applicationDataDogConfigurationFormItems = (translate: TranslateTyp }, ]; -export const applicationCreateEditItems = (translate: TranslateType) => { +export const applicationCreateEditItems = (translate: TranslateType, allBranches?: string[]) => { return [ { , 'app-git-organization': params?.['repo']?.organization, 'app-git-web-url': params?.['repo']?.webUrl, 'app-git-url': params?.['repo']?.gitUrl, - 'app-git-default-branch': params?.['repo']?.defaultBranch, + 'app-git-branches-to-audit': params?.['repo']?.branchesToAudit, 'app-contact-email': params?.['contactMail'], 'app-production-link-1': params?.['links']?.find?.( (item) => item.label === 'Application production url', @@ -297,7 +303,7 @@ export const applicationCreateOrEditFormOutputAdapter = (data: unknown): Variabl gitOrganization: params?.['app-git-organization'], gitWebUrl: params?.['app-git-web-url'], gitUrl: params?.['app-git-url'], - gitDefaultBranch: params?.['app-git-default-branch'], + gitBranchesToAudit: params?.['app-git-branches-to-audit'] as string[], name: params?.['app-name'], contactMail: params?.['app-contact-email'], productionLink: params?.['app-production-link-1'], diff --git a/v6y-apps/front-bo/src/features/v6y-applications/apis/getApplicationDetails.ts b/v6y-apps/front-bo/src/features/v6y-applications/apis/getApplicationDetails.ts index 7aa6b980..1386e91e 100644 --- a/v6y-apps/front-bo/src/features/v6y-applications/apis/getApplicationDetails.ts +++ b/v6y-apps/front-bo/src/features/v6y-applications/apis/getApplicationDetails.ts @@ -17,7 +17,8 @@ const GetApplicationDetails = gql` organization webUrl gitUrl - defaultBranch + branchesToAudit + allBranches } configuration { dataDog { diff --git a/v6y-apps/front-bo/src/features/v6y-applications/components/VitalityApplicationEditView.tsx b/v6y-apps/front-bo/src/features/v6y-applications/components/VitalityApplicationEditView.tsx index 87536c59..ae295e30 100644 --- a/v6y-apps/front-bo/src/features/v6y-applications/components/VitalityApplicationEditView.tsx +++ b/v6y-apps/front-bo/src/features/v6y-applications/components/VitalityApplicationEditView.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import { @@ -40,7 +42,12 @@ export default function VitalityApplicationEditView() { _id: parseInt(id as string, 10), }, }} - formItems={applicationCreateEditItems(translate)} + formItems={(queryData) => { + const allBranches = (queryData?.['repo'] as Record | undefined)?.[ + 'allBranches' + ] as string[] | undefined; + return applicationCreateEditItems(translate, allBranches); + }} /> ); } diff --git a/v6y-apps/front/public/locales/en/common.json b/v6y-apps/front/public/locales/en/common.json index e48dd2fa..2c3f220f 100644 --- a/v6y-apps/front/public/locales/en/common.json +++ b/v6y-apps/front/public/locales/en/common.json @@ -53,7 +53,8 @@ "branches": { "main": "main", "develop": "develop", - "release": "release" + "release": "release", + "allBranches": "All branches" }, "exportButton": "Export Reporting", "runAuditButton": "Run Audit", diff --git a/v6y-apps/front/public/locales/fr/common.json b/v6y-apps/front/public/locales/fr/common.json index 65b8045f..2f19ace7 100644 --- a/v6y-apps/front/public/locales/fr/common.json +++ b/v6y-apps/front/public/locales/fr/common.json @@ -53,7 +53,8 @@ "branches": { "main": "main", "develop": "develop", - "release": "release" + "release": "release", + "allBranches": "Toutes les branches" }, "exportButton": "Exporter le reporting", "runAuditButton": "Lancer l'audit", diff --git a/v6y-apps/front/src/features/app-details/api/getApplicationDetailsInfosByParams.ts b/v6y-apps/front/src/features/app-details/api/getApplicationDetailsInfosByParams.ts index 63524eb8..d8add5be 100644 --- a/v6y-apps/front/src/features/app-details/api/getApplicationDetailsInfosByParams.ts +++ b/v6y-apps/front/src/features/app-details/api/getApplicationDetailsInfosByParams.ts @@ -13,6 +13,7 @@ const GetApplicationDetailsInfosByParams = gql` webUrl gitUrl allBranches + branchesToAudit } links { label diff --git a/v6y-apps/front/src/features/app-details/api/triggerApplicationAnalysis.ts b/v6y-apps/front/src/features/app-details/api/triggerApplicationAnalysis.ts new file mode 100644 index 00000000..2a6ab557 --- /dev/null +++ b/v6y-apps/front/src/features/app-details/api/triggerApplicationAnalysis.ts @@ -0,0 +1,13 @@ +import { gql } from 'graphql-request'; + +const TriggerApplicationAnalysis = gql` + mutation TriggerApplicationAnalysis($applicationId: Int!) { + triggerApplicationAnalysis(applicationId: $applicationId) { + success + applicationId + message + } + } +`; + +export default TriggerApplicationAnalysis; 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..bc399220 100644 --- a/v6y-apps/front/src/features/app-details/components/VitalityAppDetailsView.tsx +++ b/v6y-apps/front/src/features/app-details/components/VitalityAppDetailsView.tsx @@ -1,5 +1,6 @@ 'use client'; +import { useQueryClient } from '@tanstack/react-query'; import * as React from 'react'; import { ApplicationType } from '@v6y/core-logic/src/types'; @@ -13,6 +14,7 @@ import { useClientQuery, } from '../../../infrastructure/adapters/api/useQueryAdapter'; import GetApplicationDetailsInfosByParams from '../api/getApplicationDetailsInfosByParams'; +import TriggerApplicationAnalysis from '../api/triggerApplicationAnalysis'; import VitalitySummaryCard from '../components/summary-card/VitalitySummaryCard'; import BranchSelector from './BranchSelector'; @@ -72,6 +74,7 @@ const getSonarQubeLink = (appInfos?: ApplicationType) => { const VitalityAppDetailsView = () => { const { getUrlParams } = useNavigationAdapter(); const { translate } = useTranslationProvider(); + const queryClient = useQueryClient(); const [_id] = getUrlParams(['_id']); const [activeTab, setActiveTab] = React.useState('overview'); const [selectedBranch, setSelectedBranch] = React.useState(''); @@ -94,19 +97,26 @@ const VitalityAppDetailsView = () => { }); const appInfos = appDetailsInfos?.getApplicationDetailsInfoByParams; - const auditReportBranches = (appInfos?.repo?.allBranches || []) as string[]; + const branchesToAudit = (appInfos?.repo?.branchesToAudit || []) as string[]; + const allBranchesLabel = translate('vitality.appDetailsPage.branches.allBranches'); + // If no specific branches are configured, show a single "all branches" option + const selectorBranches = branchesToAudit.length ? branchesToAudit : [allBranchesLabel]; React.useEffect(() => { - if (!auditReportBranches.length) { + if (!selectorBranches.length) { return; } - if (!auditReportBranches.includes(selectedBranch)) { - setSelectedBranch(auditReportBranches[0]); + if (!selectorBranches.includes(selectedBranch)) { + setSelectedBranch(selectorBranches[0]); } - }, [auditReportBranches, selectedBranch]); + }, [selectorBranches, selectedBranch]); + const sonarqubeLink = getSonarQubeLink(appInfos); + // The branch value passed to audit queries: undefined when showing "all branches" + const activeBranch = selectedBranch === allBranchesLabel ? undefined : selectedBranch; + const tabs = [ { id: 'overview', label: translate('vitality.appDetailsPage.tabs.overview') }, { id: 'performance', label: translate('vitality.appDetailsPage.tabs.performance') }, @@ -129,6 +139,13 @@ const VitalityAppDetailsView = () => { const onRunAuditClicked = async () => { setIsRunningAudit(true); try { + await buildClientQuery({ + queryBaseUrl: VitalityApiConfig.VITALITY_BFF_URL as string, + query: TriggerApplicationAnalysis, + variables: { applicationId: parseInt(_id as string, 10) }, + }); + // Invalidate all cached audit/dependency queries so they refetch fresh data + await queryClient.invalidateQueries(); setAuditTrigger((prev) => prev + 1); } catch (error) { console.error('Error running audit:', error); @@ -145,7 +162,7 @@ const VitalityAppDetailsView = () => { return ( ); @@ -154,7 +171,7 @@ const VitalityAppDetailsView = () => { ); case 'accessibility': @@ -162,19 +179,19 @@ const VitalityAppDetailsView = () => { ); case 'security': return ( - + ); case 'maintainability': return ( ); case 'greenIndex': @@ -186,7 +203,7 @@ const VitalityAppDetailsView = () => { ); case 'sonarqube': @@ -201,7 +218,7 @@ const VitalityAppDetailsView = () => { return ( @@ -234,7 +251,7 @@ const VitalityAppDetailsView = () => {
diff --git a/v6y-libs/ui-kit/src/components/organisms/admin/AdminEditWrapper.tsx b/v6y-libs/ui-kit/src/components/organisms/admin/AdminEditWrapper.tsx index 3f8e0f69..3f80c1c9 100644 --- a/v6y-libs/ui-kit/src/components/organisms/admin/AdminEditWrapper.tsx +++ b/v6y-libs/ui-kit/src/components/organisms/admin/AdminEditWrapper.tsx @@ -42,7 +42,16 @@ const AdminEditWrapper = ({ title, queryOptions, mutationOptions, formItems }: F } }, [form, query, queryOptions]); - if (!formItems?.length) { + if (!formItems) { + return null; + } + + const queryData = (query?.data?.[queryOptions?.queryResource as keyof typeof query.data] || + {}) as Record; + + const resolvedItems = typeof formItems === 'function' ? formItems(queryData) : formItems; + + if (!resolvedItems?.length) { return null; } @@ -52,7 +61,7 @@ const AdminEditWrapper = ({ title, queryOptions, mutationOptions, formItems }: F return (
- {formItems?.map((item) => item)} + {resolvedItems?.map((item) => item)}
); diff --git a/v6y-libs/ui-kit/src/components/types/FormType.ts b/v6y-libs/ui-kit/src/components/types/FormType.ts index 5d317e49..c5478d3f 100644 --- a/v6y-libs/ui-kit/src/components/types/FormType.ts +++ b/v6y-libs/ui-kit/src/components/types/FormType.ts @@ -50,7 +50,7 @@ export interface FormWrapperType { queryOptions?: FormQueryOptionsType; mutationOptions?: FormMutationOptionsType; createOptions?: FormCreateOptionsType; - formItems?: ReactNode[]; + formItems?: ReactNode[] | ((queryData: Record) => ReactNode[]); selectOptions?: { resource: string; query: string; From 9557a0d662b19fd588a8fba525955e9ddcb43cea Mon Sep 17 00:00:00 2001 From: "theo.gillet" Date: Thu, 7 May 2026 16:13:40 +0200 Subject: [PATCH 4/6] [FEAT]: added new favicon --- v6y-apps/front-bo/public/favicon.svg | 3 +++ v6y-apps/front-bo/src/app/layout.tsx | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 v6y-apps/front-bo/public/favicon.svg diff --git a/v6y-apps/front-bo/public/favicon.svg b/v6y-apps/front-bo/public/favicon.svg new file mode 100644 index 00000000..31d2f308 --- /dev/null +++ b/v6y-apps/front-bo/public/favicon.svg @@ -0,0 +1,3 @@ + + + diff --git a/v6y-apps/front-bo/src/app/layout.tsx b/v6y-apps/front-bo/src/app/layout.tsx index 795d1d17..745c1e43 100644 --- a/v6y-apps/front-bo/src/app/layout.tsx +++ b/v6y-apps/front-bo/src/app/layout.tsx @@ -8,7 +8,7 @@ export const metadata = { description: 'Vitality (v6y) is a web-based application developed by Ekino, designed to maintain and optimize the health and performance of codebases and applications.', icons: { - icon: '/favicon.ico', + icon: '/favicon.svg', }, }; From f1b9dbd7d3a9f733f384aaa21a3f28bcd2fd94cb Mon Sep 17 00:00:00 2001 From: "theo.gillet" Date: Thu, 7 May 2026 16:36:00 +0200 Subject: [PATCH 5/6] [FIX]: setupTests front --- .../src/workers/DataUpdateScheduler.ts | 7 ------- v6y-apps/front/setupTests.tsx | 16 ++++++++++++---- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/v6y-apps/bfb-main-analyzer/src/workers/DataUpdateScheduler.ts b/v6y-apps/bfb-main-analyzer/src/workers/DataUpdateScheduler.ts index f6324301..843e89c7 100644 --- a/v6y-apps/bfb-main-analyzer/src/workers/DataUpdateScheduler.ts +++ b/v6y-apps/bfb-main-analyzer/src/workers/DataUpdateScheduler.ts @@ -27,13 +27,6 @@ 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(() => { diff --git a/v6y-apps/front/setupTests.tsx b/v6y-apps/front/setupTests.tsx index e74d7cc0..b43fd17b 100644 --- a/v6y-apps/front/setupTests.tsx +++ b/v6y-apps/front/setupTests.tsx @@ -3,6 +3,7 @@ import { cleanup } from '@testing-library/react'; import { List } from '@v6y/ui-kit'; import '@v6y/ui-kit-front'; import dynamic from 'next/dynamic'; +import { lazy, Suspense } from 'react'; import { afterEach, beforeEach, vi } from 'vitest'; vi.mock('next/dynamic', async () => { @@ -432,10 +433,17 @@ vi.mock('@v6y/ui-kit', () => { TitleView: ({ title }: { title: string }) =>

{title}

, LoaderView: () =>
Loading...
, DynamicLoader: (importFn: () => Promise<{ default: React.ComponentType }>) => { - const LazyComponent = dynamic(() => importFn(), { ssr: false }); - return (props: any) => { - return ; - }; + // Create the lazy component ONCE outside the render function. + // Creating it inside the render would produce a new unresolved component on + // every re-render (e.g. from useEffect state updates), preventing waitFor from + // ever finding the resolved content. + const LazyComponent = lazy(importFn); + const WrappedComponent = (props: any) => ( + + + + ); + return WrappedComponent; }, PaginatedList: ({ dataSource, From 5444045bb0eff7d62ffdff4fa5610702970be51c Mon Sep 17 00:00:00 2001 From: "theo.gillet" Date: Fri, 29 May 2026 11:16:21 +0200 Subject: [PATCH 6/6] [FIX]: revert unrelated changes --- pnpm-lock.yaml | 12 ++++++------ v6y-apps/bfb-dynamic-auditor/package.json | 2 +- v6y-apps/bfb-dynamic-auditor/src/types/ecoindex.d.ts | 6 +++--- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2cf0b7e0..25cc7195 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -192,8 +192,8 @@ importers: specifier: '=4.1.0' version: 4.1.0 ecoindex: - specifier: '=2.0.1' - version: 2.0.1 + specifier: '=1.0.7' + version: 1.0.7 express: specifier: '=5.2.0' version: 5.2.0 @@ -6897,9 +6897,9 @@ packages: ecdsa-sig-formatter@1.0.11: resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} - ecoindex@2.0.1: - resolution: {integrity: sha512-62wNhERrLcrJ7ItG1BXCOEGZCWt2f9wHq1nXua73tQ/2PKV1vup5Kj2bQiqQxBzslmNYQ8H79CQOtyHbmiQqxA==} - engines: {node: '>=18.20.0'} + ecoindex@1.0.7: + resolution: {integrity: sha512-0fJWWWuJ2T9ykpvZ0O6MJX/qMjZ/Z17s4tWvfEWxAuX8YuhVKZ2LXdrUu67cs2FFdHck2oeUP7Uf4NGefGdQfQ==} + engines: {node: '>=16.0.0 <18.20.0'} ecoindex_reference@1.0.2: resolution: {integrity: sha512-IKse3tu6IQfhrpGsVuxmC1K4xlc1FCZtaNYjLUj+Yxm5wthuVyS7C/miVPqSN7UZaNViUO2013ri+WJJhZfCUA==} @@ -18368,7 +18368,7 @@ snapshots: dependencies: safe-buffer: 5.2.1 - ecoindex@2.0.1: + ecoindex@1.0.7: dependencies: ecoindex_reference: 1.0.2 diff --git a/v6y-apps/bfb-dynamic-auditor/package.json b/v6y-apps/bfb-dynamic-auditor/package.json index d301d7f3..d6887057 100644 --- a/v6y-apps/bfb-dynamic-auditor/package.json +++ b/v6y-apps/bfb-dynamic-auditor/package.json @@ -58,7 +58,7 @@ "cors": "=2.8.5", "cron": "=4.3.3", "date-fns": "=4.1.0", - "ecoindex": "=2.0.1", + "ecoindex": "=1.0.7", "express": "=5.2.0", "express-status-monitor": "=1.3.4", "fs-extra": "=11.2.0", diff --git a/v6y-apps/bfb-dynamic-auditor/src/types/ecoindex.d.ts b/v6y-apps/bfb-dynamic-auditor/src/types/ecoindex.d.ts index 93c48ebd..7654f0a5 100644 --- a/v6y-apps/bfb-dynamic-auditor/src/types/ecoindex.d.ts +++ b/v6y-apps/bfb-dynamic-auditor/src/types/ecoindex.d.ts @@ -1,9 +1,9 @@ declare module 'ecoindex' { export function computeEcoIndex(dom: number, req: number, size: number): number; - export function computeQuantile(quantiles: number[], value: number): number; export function getEcoIndexGrade(ecoIndex: number): string | false; - export function getEcoIndexGradesList(): { value: number; grade: string }[]; - export function getQuantiles(): { dom: number[]; req: number[]; size: number[] }; export function computeGreenhouseGasesEmissionfromEcoIndex(ecoIndex: number): string; export function computeWaterConsumptionfromEcoIndex(ecoIndex: number): string; + export function computeQuantile(quantiles: number[], value: number): number; + export function getQuantiles(): { dom: number[]; req: number[]; size: number[] }; + export function getEcoIndexGradesList(): { grade: string; value: number }[]; }