diff --git a/ExpressAPI/assets/OpenAPI/OpenAPI.yaml b/ExpressAPI/assets/OpenAPI/OpenAPI.yaml index 43d8d1e90d9956ae2942536cf0b99d469dfa8188..1d945690c02269c9dbffe84e8264f30e45af74f9 100644 --- a/ExpressAPI/assets/OpenAPI/OpenAPI.yaml +++ b/ExpressAPI/assets/OpenAPI/OpenAPI.yaml @@ -110,6 +110,13 @@ paths: type: boolean examples: - true + languages: + type: array + items: string + examples: + - - c + - cpp + - js description: OK default: $ref: '#/components/responses/ERROR' @@ -333,6 +340,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 ee2c0f1197d7edab927913132056f2d21725ef47..764785be5cc73ccb019226481cf393ffe853b43f 100644 --- a/ExpressAPI/prisma/schema.prisma +++ b/ExpressAPI/prisma/schema.prisma @@ -48,6 +48,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 7a42824e2bb3c51f3585e757474d980599919e48..a5459275aa886d175cae0015b7919901828db779 100644 --- a/ExpressAPI/src/managers/AssignmentManager.ts +++ b/ExpressAPI/src/managers/AssignmentManager.ts @@ -26,7 +26,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: { @@ -35,11 +35,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 16431c8e06396195982dbcef8488602e46f9e326..e7d8c13d2f9abfbc5a80db9dc0314c7eca2f3289 100644 --- a/ExpressAPI/src/routes/AssignmentRoutes.ts +++ b/ExpressAPI/src/routes/AssignmentRoutes.ts @@ -26,6 +26,7 @@ import SharedConfig from '../shared/config/SharedConfig.js'; import SharedSonarManager from '../shared/managers/SharedSonarManager'; import SonarProjectCreation from '../shared/types/Sonar/SonarProjectCreation'; import SonarManager from '../managers/SonarManager'; +import { v4 as uuidv4 } from 'uuid'; class AssignmentRoutes implements RoutesManager { @@ -143,7 +144,7 @@ class AssignmentRoutes implements RoutesManager { private async createAssignment(req: express.Request, res: express.Response) { const params: { - name: string, members: Array<Gitlab.UserSchema>, template: string, useSonar: string, language: string + name: string, members: Array<Gitlab.UserSchema>, template: string, useSonar: string, sonarGate: string, sonarProfiles: string, language: string } = req.body; const useSonar = params.useSonar === 'true'; @@ -197,7 +198,12 @@ class AssignmentRoutes implements RoutesManager { // Create Sonar project let sonarProject: SonarProjectCreation | undefined = undefined; if ( useSonar ) { - sonarProject = await GlobalHelper.repoCreationFnExecCreator(req, res, DojoStatusCode.ASSIGNMENT_CREATION_SONAR_ERROR, DojoStatusCode.ASSIGNMENT_CREATION_INTERNAL_ERROR, repository)(() => SonarManager.createProjectFromGitlab(repository.id), 'Sonar project creation error') as SonarProjectCreation; + const profiles: string[] = JSON.parse(params.sonarProfiles); + sonarProject = await GlobalHelper.repoCreationFnExecCreator(req, res, DojoStatusCode.ASSIGNMENT_CREATION_SONAR_ERROR, DojoStatusCode.ASSIGNMENT_CREATION_INTERNAL_ERROR, repository)(() => SonarManager.createProjectWithQualities(repository, params.sonarGate, profiles, req, res), 'Sonar project creation error') as SonarProjectCreation; + + if ( sonarProject == undefined ) { + return; + } } const assignment: Assignment = await repoCreationFnExec(() => db.assignment.create({ @@ -212,6 +218,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 6c7d139a8c57819b7a7dadef0a64b36b0b0de29f..6221edeff897ee59a45a2a42fa784aa28974db48 100644 --- a/ExpressAPI/src/routes/ExerciseRoutes.ts +++ b/ExpressAPI/src/routes/ExerciseRoutes.ts @@ -270,8 +270,13 @@ class ExerciseRoutes implements RoutesManager { // Create Sonar project let sonarProject: SonarProjectCreation | undefined = undefined; - if ( assignment.useSonar ) { - sonarProject = await GlobalHelper.repoCreationFnExecCreator(req, res, DojoStatusCode.EXERCISE_CREATION_SONAR_ERROR, DojoStatusCode.EXERCISE_CREATION_INTERNAL_ERROR, repository)(() => SonarManager.createProjectFromGitlab(repository.id), 'Sonar project creation error') as SonarProjectCreation; + if ( assignment.useSonar && assignment.sonarProfiles != null ) { + const profiles: string[] = JSON.parse(assignment.sonarProfiles as string); + sonarProject = await GlobalHelper.repoCreationFnExecCreator(req, res, DojoStatusCode.EXERCISE_CREATION_SONAR_ERROR, DojoStatusCode.EXERCISE_CREATION_INTERNAL_ERROR, repository)(() => SonarManager.createProjectWithQualities(repository, assignment.sonarGate, profiles, req, res), 'Sonar project creation error') as SonarProjectCreation; + + if ( sonarProject == undefined ) { + return; + } }