diff --git a/ExpressAPI/assets/OpenAPI/OpenAPI.yaml b/ExpressAPI/assets/OpenAPI/OpenAPI.yaml index ee331c56f8f99afbf3c871e03a38830d0e9e5224..c435bfe5e20e5b8e246c1b60acc610f95b197900 100644 --- a/ExpressAPI/assets/OpenAPI/OpenAPI.yaml +++ b/ExpressAPI/assets/OpenAPI/OpenAPI.yaml @@ -70,6 +70,13 @@ paths: type: boolean examples: - true + languages: + type: array + items: string + examples: + - - c + - cpp + - js description: OK default: $ref: '#/components/responses/ERROR' @@ -303,6 +310,16 @@ paths: type: boolean default: false description: Whether or not to use the Sonar integration to validate the assignment and exercise solutions + sonarGate: + type: string + default: "" + description: Sonar quality gate to use for the assignment + sonarProfiles: + type: string + default: "[]" + description: Sonar quality profiles for the assignment, as a JSON array of strings + examples: + - "[\"Sonar Way\", \"ArchWeb Way\"]" language: type: string default: other diff --git a/ExpressAPI/prisma/migrations/20240430135259_add_sonar_quality/migration.sql b/ExpressAPI/prisma/migrations/20240430135259_add_sonar_quality/migration.sql new file mode 100644 index 0000000000000000000000000000000000000000..26e0361c56e525dfe04d56f2a00906842b62ca7b --- /dev/null +++ b/ExpressAPI/prisma/migrations/20240430135259_add_sonar_quality/migration.sql @@ -0,0 +1,3 @@ +-- AlterTable +ALTER TABLE `Assignment` ADD COLUMN `sonarGate` VARCHAR(191) NULL, + ADD COLUMN `sonarProfiles` JSON NULL; diff --git a/ExpressAPI/prisma/schema.prisma b/ExpressAPI/prisma/schema.prisma index b647317033ed8da0456e13303ff07c8abe29b0ae..87bf7d41c679ba53141669a0b538c6a5c5c41ccd 100644 --- a/ExpressAPI/prisma/schema.prisma +++ b/ExpressAPI/prisma/schema.prisma @@ -40,6 +40,8 @@ model Assignment { useSonar Boolean @default(false) sonarKey String? sonarCreationInfo Json? @db.Json + sonarGate String? + sonarProfiles Json? @db.Json exercises Exercise[] staff User[] diff --git a/ExpressAPI/src/managers/AssignmentManager.ts b/ExpressAPI/src/managers/AssignmentManager.ts index ff16f6b1b60250b8729ea4a13811c6637654bd62..c405c9d2ec4c6335b4008521f97eede071d0bd4d 100644 --- a/ExpressAPI/src/managers/AssignmentManager.ts +++ b/ExpressAPI/src/managers/AssignmentManager.ts @@ -25,7 +25,7 @@ class AssignmentManager { async getByGitlabLink(gitlabLink: string, include: Prisma.AssignmentInclude | undefined = undefined): Promise<Assignment | undefined> { const nameInUrl = gitlabLink.replace('.git', '').split('/').pop()!; - + console.log(nameInUrl); const result = await db.assignment.findMany({ where : { gitlabLink: { @@ -34,11 +34,12 @@ class AssignmentManager { }, include: include }) as Array<Assignment>; - + console.log(result); return result.length > 0 ? result[0] : undefined; } get(nameOrUrl: string, include: Prisma.AssignmentInclude | undefined = undefined): Promise<Assignment | undefined> { + console.log(nameOrUrl); if ( nameOrUrl.includes('://') ) { return this.getByGitlabLink(nameOrUrl, include); } else { diff --git a/ExpressAPI/src/managers/SonarManager.ts b/ExpressAPI/src/managers/SonarManager.ts index 3a5808c072423bc1108b83d5f474ce1060668a47..88320a4cca56bc618ef58c066a70e5df661a13ba 100644 --- a/ExpressAPI/src/managers/SonarManager.ts +++ b/ExpressAPI/src/managers/SonarManager.ts @@ -1,9 +1,13 @@ -import SharedConfig from '../shared/config/SharedConfig'; +import SharedConfig from '../shared/config/SharedConfig'; import SonarRoute from '../shared/types/Sonar/SonarRoute'; import axios, { AxiosInstance } from 'axios'; import Config from '../config/Config'; -import SonarProjectCreation from '../shared/types/Sonar/SonarProjectCreation'; -import https from 'https'; +import SonarProjectCreation from '../shared/types/Sonar/SonarProjectCreation'; +import https from 'https'; +import GlobalHelper from '../helpers/GlobalHelper'; +import DojoStatusCode from '../shared/types/Dojo/DojoStatusCode'; +import express from 'express'; +import GitlabRepository from '../shared/types/Gitlab/GitlabRepository'; class SonarManager { @@ -59,6 +63,60 @@ class SonarManager { return await this.executePostRequest<SonarProjectCreation>(this.getApiUrl(SonarRoute.PROJECT_CREATE_GITLAB), formData) } + async addQualityGate(projectKey: string, qualityGate: string) { + const formData = new FormData(); + formData.append('projectKey', projectKey); + formData.append('gateName', qualityGate); + + return await this.executePostRequest<undefined>(this.getApiUrl(SonarRoute.PROJECT_ADD_GATE), formData); + } + + async addQualityProfile(projectKey: string, qualityProfile: string, language: string) { + const formData = new FormData(); + formData.append('project', projectKey); + formData.append('qualityProfile', qualityProfile); + formData.append('language', language); + + return await this.executePostRequest<unknown>(this.getApiUrl(SonarRoute.PROJECT_ADD_PROFILE), formData); + } + + async createProjectWithQualities(gitlabRepository: GitlabRepository, qualityGate: string | null, qualityProfiles: string[] | null, req: express.Request, res: express.Response) { + let sonarProject: SonarProjectCreation | undefined = undefined; + try { + sonarProject = await this.createProjectFromGitlab(gitlabRepository.id); + if (sonarProject == undefined) { + return await GlobalHelper.repositoryCreationError('Sonar error', undefined, req, res, DojoStatusCode.ASSIGNMENT_CREATION_SONAR_ERROR, DojoStatusCode.ASSIGNMENT_CREATION_SONAR_ERROR, gitlabRepository); + } + } catch ( error ) { + return await GlobalHelper.repositoryCreationError('Sonar project creation error', error, req, res, DojoStatusCode.ASSIGNMENT_CREATION_SONAR_ERROR, DojoStatusCode.ASSIGNMENT_CREATION_INTERNAL_ERROR, gitlabRepository); + } + // Add gate and profiles to sonar project + if ( qualityGate != undefined && qualityGate != "" ) { + try { + await this.addQualityGate(sonarProject.project.key, qualityGate); + } catch ( error ) { + return await GlobalHelper.repositoryCreationError('Sonar gate error', error, req, res, DojoStatusCode.ASSIGNMENT_SONAR_GATE_NOT_FOUND, DojoStatusCode.ASSIGNMENT_SONAR_GATE_NOT_FOUND, gitlabRepository); + } + } + + if ( qualityProfiles != undefined && qualityProfiles.length > 0 ) { + for ( const profile of qualityProfiles ) { + try { + const [ lang, name ] = profile.split('/'); + if (lang.trim() != '' && name.trim() != '') { + await this.addQualityProfile(sonarProject.project.key, name.trim(), lang.trim()); + } else { + return await GlobalHelper.repositoryCreationError('Sonar profile invalid', undefined, req, res, DojoStatusCode.ASSIGNMENT_SONAR_PROFILE_NOT_FOUND, DojoStatusCode.ASSIGNMENT_SONAR_PROFILE_NOT_FOUND, gitlabRepository); + } + } catch ( error ) { + return await GlobalHelper.repositoryCreationError('Sonar profile not found', error, req, res, DojoStatusCode.ASSIGNMENT_SONAR_PROFILE_NOT_FOUND, DojoStatusCode.ASSIGNMENT_SONAR_PROFILE_NOT_FOUND, gitlabRepository); + } + } + } + + return sonarProject; + } + async getLanguages() { const resp = await this.executeGetRequest<{ languages: { key: string, name: string }[]}>(this.getApiUrl(SonarRoute.GET_LANGUAGES)) return resp.languages.map(l => l.key) diff --git a/ExpressAPI/src/routes/AssignmentRoutes.ts b/ExpressAPI/src/routes/AssignmentRoutes.ts index 3423f5bed69ce52544d2b80dfc4edaa1bfebba2f..3229bb16b93c8f1e78e8a13b812dc0581203c61e 100644 --- a/ExpressAPI/src/routes/AssignmentRoutes.ts +++ b/ExpressAPI/src/routes/AssignmentRoutes.ts @@ -101,7 +101,7 @@ class AssignmentRoutes implements RoutesManager { private async createAssignment(req: express.Request, res: express.Response) { const params: { - name: string, members: Array<GitlabUser>, template: string, useSonar: string, language: string + name: string, members: Array<GitlabUser>, template: string, useSonar: string, sonarGate: string, sonarProfiles: string, language: string } = req.body; const useSonar = params.useSonar === 'true'; @@ -172,12 +172,13 @@ class AssignmentRoutes implements RoutesManager { // Create Sonar project let sonarProject: SonarProjectCreation | undefined = undefined; if ( useSonar ) { - try { - sonarProject = await SonarManager.createProjectFromGitlab(repository.id); - } catch ( error ) { - logger.error('Sonar project creation error'); - logger.error(error); - return GlobalHelper.repositoryCreationError('Sonar error', error, req, res, DojoStatusCode.ASSIGNMENT_CREATION_SONAR_ERROR, DojoStatusCode.ASSIGNMENT_CREATION_INTERNAL_ERROR, repository); + const profiles: string[] = JSON.parse(params.sonarProfiles); + const resp = await SonarManager.createProjectWithQualities(repository, params.sonarGate, profiles, req, res); + if (resp == undefined) { + return resp; + + } else { + sonarProject = resp; } } @@ -193,6 +194,8 @@ class AssignmentRoutes implements RoutesManager { useSonar : useSonar, sonarKey : sonarProject?.project.key, sonarCreationInfo : sonarProject?.project, + sonarGate : params.sonarGate, + sonarProfiles : params.sonarProfiles, language : Language[params.language as keyof typeof Language], staff : { connectOrCreate: [ ...params.members.map(gitlabUser => { diff --git a/ExpressAPI/src/routes/ExerciseRoutes.ts b/ExpressAPI/src/routes/ExerciseRoutes.ts index fe22cabf138b4f8c083b89966396afb0cdcb37f5..f56c3c4fd3e431d2a858f94abc46a6bef9659969 100644 --- a/ExpressAPI/src/routes/ExerciseRoutes.ts +++ b/ExpressAPI/src/routes/ExerciseRoutes.ts @@ -171,13 +171,14 @@ class ExerciseRoutes implements RoutesManager { // Create Sonar project let sonarProject: SonarProjectCreation | undefined = undefined; - if (assignment.useSonar) { - try { - sonarProject = await SonarManager.createProjectFromGitlab(repository.id); - } catch ( error ) { - logger.error("Sonar project creation error"); - logger.error(error); - return GlobalHelper.repositoryCreationError('Sonar error', error, req, res, DojoStatusCode.EXERCISE_CREATION_SONAR_ERROR, DojoStatusCode.ASSIGNMENT_CREATION_INTERNAL_ERROR, repository); + console.log(assignment, repository); + if (assignment.useSonar && assignment.sonarProfiles != null) { + const profiles: string[] = JSON.parse(assignment.sonarProfiles as string); + const resp = await SonarManager.createProjectWithQualities(repository, assignment.sonarGate, profiles, req, res); + if (resp == undefined) { + return resp; + } else { + sonarProject = resp; } } diff --git a/ExpressAPI/src/shared b/ExpressAPI/src/shared index 7f368ef971c8bbe9c5c693a6a8cf733f2ed1e3d2..102e79d9b78d79e495f7b82e5e767eb7898248db 160000 --- a/ExpressAPI/src/shared +++ b/ExpressAPI/src/shared @@ -1 +1 @@ -Subproject commit 7f368ef971c8bbe9c5c693a6a8cf733f2ed1e3d2 +Subproject commit 102e79d9b78d79e495f7b82e5e767eb7898248db