From 38ee0bfe00d083d3ddc2dbdfde2d2b0fbcfc19a7 Mon Sep 17 00:00:00 2001 From: Joel von der Weid <joel.von-der-weid@hesge.ch> Date: Mon, 6 May 2024 16:07:08 +0200 Subject: [PATCH] Merge from origin --- ExpressAPI/assets/OpenAPI/OpenAPI.yaml | 17 +++++ .../migration.sql | 3 + ExpressAPI/prisma/schema.prisma | 2 + ExpressAPI/src/managers/AssignmentManager.ts | 5 +- ExpressAPI/src/managers/SonarManager.ts | 64 ++++++++++++++++++- ExpressAPI/src/routes/AssignmentRoutes.ts | 12 +++- ExpressAPI/src/routes/ExerciseRoutes.ts | 9 ++- 7 files changed, 103 insertions(+), 9 deletions(-) create mode 100644 ExpressAPI/prisma/migrations/20240430135259_add_sonar_quality/migration.sql diff --git a/ExpressAPI/assets/OpenAPI/OpenAPI.yaml b/ExpressAPI/assets/OpenAPI/OpenAPI.yaml index 43d8d1e..1d94569 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 0000000..26e0361 --- /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 ee2c0f1..764785b 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 7a42824..a545927 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 3a5808c..88320a4 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 16431c8..e7d8c13 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 6c7d139..6221ede 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; + } } -- GitLab