From b930db3f63e53b9404b95ee1fb4d4362d6726cba 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 | 17 +++-- ExpressAPI/src/routes/ExerciseRoutes.ts | 15 +++-- ExpressAPI/src/shared | 2 +- 8 files changed, 105 insertions(+), 20 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 ee331c5..c435bfe 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 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 b647317..87bf7d4 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 ff16f6b..c405c9d 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 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 3423f5b..3229bb1 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 fe22cab..f56c3c4 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 7f368ef..102e79d 160000 --- a/ExpressAPI/src/shared +++ b/ExpressAPI/src/shared @@ -1 +1 @@ -Subproject commit 7f368ef971c8bbe9c5c693a6a8cf733f2ed1e3d2 +Subproject commit 102e79d9b78d79e495f7b82e5e767eb7898248db -- GitLab