Skip to content

Commit

Permalink
Programming exercises: Allow instructors to check the consistency of …
Browse files Browse the repository at this point in the history
…repositories (version control) and build plans (continuous integration) (ls1intum#3840)
  • Loading branch information
daniels98it authored Nov 8, 2021
1 parent b1de2af commit b50de55
Show file tree
Hide file tree
Showing 19 changed files with 883 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,14 @@ SELECT COUNT (DISTINCT p) FROM ProgrammingExerciseStudentParticipation p
""")
List<ProgrammingExercise> findAllProgrammingExercisesInCourseOrInExamsOfCourse(@Param("course") Course course);

@Query("""
SELECT pe FROM ProgrammingExercise pe
LEFT JOIN FETCH pe.templateParticipation tp
LEFT JOIN FETCH pe.solutionParticipation sp
WHERE pe.course.id = :#{#courseId}
""")
List<ProgrammingExercise> findAllByCourseWithTemplateAndSolutionParticipation(@Param("courseId") Long courseId);

long countByShortNameAndCourse(String shortName, Course course);

long countByTitleAndCourse(String shortName, Course course);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package de.tum.in.www1.artemis.service;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import org.springframework.stereotype.Service;

import de.tum.in.www1.artemis.domain.ProgrammingExercise;
import de.tum.in.www1.artemis.repository.ProgrammingExerciseRepository;
import de.tum.in.www1.artemis.service.connectors.ContinuousIntegrationService;
import de.tum.in.www1.artemis.service.connectors.VersionControlService;
import de.tum.in.www1.artemis.service.dto.ConsistencyErrorDTO;

/**
* Service Implementation for consistency checks
* of programming exercises
*/
@Service
public class ConsistencyCheckService {

private final Optional<VersionControlService> versionControlService;

private final Optional<ContinuousIntegrationService> continuousIntegrationService;

private final ProgrammingExerciseRepository programmingExerciseRepository;

public ConsistencyCheckService(Optional<VersionControlService> versionControlService, Optional<ContinuousIntegrationService> continuousIntegrationService,
ProgrammingExerciseRepository programmingExerciseRepository) {
this.versionControlService = versionControlService;
this.continuousIntegrationService = continuousIntegrationService;
this.programmingExerciseRepository = programmingExerciseRepository;
}

/**
* Overloaded method for consistency checks, which retrieves the programming exercise before calling
* the consistency checks method
*
* @param exerciseId of the programming exercise to check
* @return List containing the resulting errors, if any.
*/
public List<ConsistencyErrorDTO> checkConsistencyOfProgrammingExercise(long exerciseId) {
ProgrammingExercise programmingExercise = programmingExerciseRepository.findByIdWithTemplateAndSolutionParticipationElseThrow(exerciseId);

return checkConsistencyOfProgrammingExercise(programmingExercise);
}

/**
* Performs multiple checks for a given programming exercise and returns a list
* of the resulting errors, if any.
*
* Make sure to load Template and Solution participations along with the programming exercise.
*
* Checks:
* - Existence of VCS repositories and project
* - Existence of CI build plans
*
* @param programmingExercise of the programming exercise to check
* @return List containing the resulting errors, if any.
*/
public List<ConsistencyErrorDTO> checkConsistencyOfProgrammingExercise(ProgrammingExercise programmingExercise) {
List<ConsistencyErrorDTO> result = new ArrayList<>();

programmingExercise.checksAndSetsIfProgrammingExerciseIsLocalSimulation();
if (programmingExercise.getIsLocalSimulation()) {
result.add(new ConsistencyErrorDTO(programmingExercise, ConsistencyErrorDTO.ErrorType.IS_LOCAL_SIMULATION));
return result;
}

result.addAll(checkVCSConsistency(programmingExercise));
result.addAll(checkCIConsistency(programmingExercise));

return result;
}

/**
* Checks if a project and its repositories (TEMPLATE, TEST, SOLUTION) exists in the VCS
* for a given programming exercise.
* @param programmingExercise to check
* @return List containing the resulting errors, if any.
*/
private List<ConsistencyErrorDTO> checkVCSConsistency(ProgrammingExercise programmingExercise) {
List<ConsistencyErrorDTO> result = new ArrayList<>();

if (!versionControlService.get().checkIfProjectExists(programmingExercise.getProjectKey(), programmingExercise.getProjectName())) {
result.add(new ConsistencyErrorDTO(programmingExercise, ConsistencyErrorDTO.ErrorType.VCS_PROJECT_MISSING));
}
else {
if (!versionControlService.get().repositoryUrlIsValid(programmingExercise.getVcsTemplateRepositoryUrl())) {
result.add(new ConsistencyErrorDTO(programmingExercise, ConsistencyErrorDTO.ErrorType.TEMPLATE_REPO_MISSING));
}
if (!versionControlService.get().repositoryUrlIsValid(programmingExercise.getVcsTestRepositoryUrl())) {
result.add(new ConsistencyErrorDTO(programmingExercise, ConsistencyErrorDTO.ErrorType.TEST_REPO_MISSING));
}
if (!versionControlService.get().repositoryUrlIsValid(programmingExercise.getVcsSolutionRepositoryUrl())) {
result.add(new ConsistencyErrorDTO(programmingExercise, ConsistencyErrorDTO.ErrorType.SOLUTION_REPO_MISSING));
}
}
return result;
}

/**
* Checks if build plans (TEMPLATE, SOLUTION) exist in the CI for a given
* programming exercise.
* @param programmingExercise to check
* @return List containing the resulting errors, if any.
*/
private List<ConsistencyErrorDTO> checkCIConsistency(ProgrammingExercise programmingExercise) {
List<ConsistencyErrorDTO> result = new ArrayList<>();

if (!continuousIntegrationService.get().checkIfBuildPlanExists(programmingExercise.getProjectKey(), programmingExercise.getTemplateBuildPlanId())) {
result.add(new ConsistencyErrorDTO(programmingExercise, ConsistencyErrorDTO.ErrorType.TEMPLATE_BUILD_PLAN_MISSING));
}
if (!continuousIntegrationService.get().checkIfBuildPlanExists(programmingExercise.getProjectKey(), programmingExercise.getSolutionBuildPlanId())) {
result.add(new ConsistencyErrorDTO(programmingExercise, ConsistencyErrorDTO.ErrorType.SOLUTION_BUILD_PLAN_MISSING));
}

return result;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package de.tum.in.www1.artemis.service.dto;

import java.util.Objects;

import de.tum.in.www1.artemis.domain.ProgrammingExercise;

/**
* A DTO representing a consistency error
*/
public class ConsistencyErrorDTO {

private ProgrammingExercise programmingExercise;

private ErrorType type;

public ConsistencyErrorDTO(ProgrammingExercise programmingExercise, ErrorType type) {
this.programmingExercise = programmingExercise;
this.type = type;
}

public ProgrammingExercise getProgrammingExercise() {
return programmingExercise;
}

public void setProgrammingExercise(ProgrammingExercise programmingExercise) {
this.programmingExercise = programmingExercise;
}

public ErrorType getType() {
return type;
}

public void setType(ErrorType type) {
this.type = type;
}

public enum ErrorType {

IS_LOCAL_SIMULATION("IS_LOCAL_SIMULATION"), VCS_PROJECT_MISSING("VCS_PROJECT_MISSING"), TEMPLATE_REPO_MISSING("TEMPLATE_REPO_MISSING"),
SOLUTION_REPO_MISSING("SOLUTION_REPO_MISSING"), TEST_REPO_MISSING("TEST_REPO_MISSING"), TEMPLATE_BUILD_PLAN_MISSING("TEMPLATE_BUILD_PLAN_MISSING"),
SOLUTION_BUILD_PLAN_MISSING("SOLUTION_BUILD_PLAN_MISSING");

ErrorType(final String value) {
// This empty constructor is added to avoid Codacy issues
}

@Override
public String toString() {
return name();
}
}

@Override
public boolean equals(Object object) {
if (this == object) {
return true;
}
if (object == null || getClass() != object.getClass()) {
return false;
}
ConsistencyErrorDTO that = (ConsistencyErrorDTO) object;
return Objects.equals(getProgrammingExercise(), that.getProgrammingExercise()) && getType() == that.getType();
}

@Override
public int hashCode() {
return Objects.hash(getProgrammingExercise(), getType());
}

@Override
public String toString() {
return "ConsistencyErrorDTO{" + "programmingExercise='" + programmingExercise.getTitle() + "', type='" + type.name() + "'}";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package de.tum.in.www1.artemis.web.rest;

import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import de.tum.in.www1.artemis.domain.Exercise;
import de.tum.in.www1.artemis.repository.ExerciseRepository;
import de.tum.in.www1.artemis.security.Role;
import de.tum.in.www1.artemis.service.AuthorizationCheckService;
import de.tum.in.www1.artemis.service.ConsistencyCheckService;
import de.tum.in.www1.artemis.service.dto.ConsistencyErrorDTO;

/**
* REST controller for consistency checks
*/
@RestController
@RequestMapping("api/")
@PreAuthorize("hasRole('INSTRUCTOR')")
public class ConsistencyCheckResource {

private final Logger log = LoggerFactory.getLogger(ConsistencyCheckResource.class);

private final AuthorizationCheckService authCheckService;

private final ConsistencyCheckService consistencyCheckService;

private final ExerciseRepository exerciseRepository;

public ConsistencyCheckResource(AuthorizationCheckService authCheckService, ConsistencyCheckService consistencyCheckService, ExerciseRepository exerciseRepository) {
this.authCheckService = authCheckService;
this.consistencyCheckService = consistencyCheckService;
this.exerciseRepository = exerciseRepository;
}

/**
* GET programming-exercises/{programmingExerciseId}/consistency-check : request consistency check for a programming exercise
* @param programmingExerciseId id of the exercise to check
* @return List containing the resulting errors, if any.
*/
@GetMapping("programming-exercises/{programmingExerciseId}/consistency-check")
public ResponseEntity<List<ConsistencyErrorDTO>> checkConsistencyOfProgrammingExercise(@PathVariable long programmingExerciseId) {
log.debug("REST request to check consistencies of programming exercise [{}]", programmingExerciseId);
final Exercise exercise = exerciseRepository.findByIdElseThrow(programmingExerciseId);
authCheckService.checkHasAtLeastRoleForExerciseElseThrow(Role.INSTRUCTOR, exercise, null);
List<ConsistencyErrorDTO> result = consistencyCheckService.checkConsistencyOfProgrammingExercise(programmingExerciseId);
return ResponseEntity.ok(result);
}
}
17 changes: 17 additions & 0 deletions src/main/webapp/app/entities/consistency-check-result.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { BaseEntity } from 'app/shared/model/base-entity';
import { ProgrammingExercise } from 'app/entities/programming-exercise.model';

export const enum ErrorType {
TEMPLATE_REPO_MISSING = 'TEMPLATE_REPO_MISSING',
SOLUTION_REPO_MISSING = 'SOLUTION_REPO_MISSING',
TEST_REPO_MISSING = 'TEST_REPO_MISSING',
TEMPLATE_BUILD_PLAN_MISSING = 'TEMPLATE_BUILD_PLAN_MISSING',
SOLUTION_BUILD_PLAN_MISSING = 'SOLUTION_BUILD_PLAN_MISSING',
}

export class ConsistencyCheckError implements BaseEntity {
public id?: number;
public type?: ErrorType;
public programmingExercise?: ProgrammingExercise;
public programmingExerciseCourseId?: number;
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,15 @@ <h2><span jhiTranslate="artemisApp.programmingExercise.detail.title">Programming
>
<span jhiTranslate="artemisApp.programmingExercise.checkPlagiarism">Check Plagiarism</span>
</a>
<button
[jhiFeatureToggle]="FeatureToggle.PROGRAMMING_EXERCISES"
*ngIf="programmingExercise.isAtLeastInstructor"
(click)="checkConsistencies(programmingExercise)"
class="btn btn-outline-primary btn-sm me-1"
style="margin-bottom: 10px"
>
<fa-icon [icon]="'check-double'"></fa-icon> <span jhiTranslate="artemisApp.consistencyCheck.button">Check consistency</span>
</button>
</div>
<div>
<a *ngIf="programmingExercise.isAtLeastEditor" [routerLink]="baseResource + 'scores'" class="btn btn-info btn-sm me-1" style="margin-bottom: 10px">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { SortService } from 'app/shared/service/sort.service';
import { Submission } from 'app/entities/submission.model';
import { EventManager } from 'app/core/util/event-manager.service';
import { createBuildPlanUrl } from 'app/exercises/programming/shared/utils/programming-exercise.utils';
import { ConsistencyCheckComponent } from 'app/shared/consistency-check/consistency-check.component';
import { SubmissionPolicyService } from 'app/exercises/programming/manage/services/submission-policy.service';

@Component({
Expand Down Expand Up @@ -309,6 +310,15 @@ export class ProgrammingExerciseDetailComponent implements OnInit, OnDestroy {
);
}

/**
* Opens modal and executes a consistency check for the given programming exercise
* @param exerciseId id of the programming exercise to check
*/
checkConsistencies(exercise: ProgrammingExercise) {
const modalRef = this.modalService.open(ConsistencyCheckComponent, { keyboard: true, size: 'lg' });
modalRef.componentInstance.exercisesToCheck = Array.of(exercise);
}

private onError(error: HttpErrorResponse) {
this.alertService.error(error.message);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,10 @@ <h4 class="d-flex mb-3 flex-wrap justify-content-end">
<span class="d-none d-md-inline" jhiTranslate="instructorDashboard.exportSubmissions.title">Download Repos</span>
</button>
<jhi-exercise-scores-export-button *ngIf="course.isAtLeastInstructor" [exercises]="selectedProgrammingExercises"></jhi-exercise-scores-export-button>
<button *ngIf="course.isAtLeastInstructor" (click)="checkConsistencies()" class="btn btn-outline-primary btn-sm me-1">
<fa-icon [icon]="'check-double'"></fa-icon>
<span jhiTranslate="artemisApp.consistencyCheck.button">Check consistency</span>
</button>
</div>
</div>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { AlertService } from 'app/core/util/alert.service';
import { EventManager } from 'app/core/util/event-manager.service';
import { createBuildPlanUrl } from 'app/exercises/programming/shared/utils/programming-exercise.utils';
import { ProfileService } from 'app/shared/layouts/profiles/profile.service';
import { ConsistencyCheckComponent } from 'app/shared/consistency-check/consistency-check.component';

@Component({
selector: 'jhi-programming-exercise',
Expand Down Expand Up @@ -162,9 +163,8 @@ export class ProgrammingExerciseComponent extends ExerciseComponent implements O
}

toggleAllProgrammingExercises() {
if (this.allChecked) {
this.selectedProgrammingExercises = [];
} else {
this.selectedProgrammingExercises = [];
if (!this.allChecked) {
this.selectedProgrammingExercises = this.selectedProgrammingExercises.concat(this.programmingExercises);
}
this.allChecked = !this.allChecked;
Expand Down Expand Up @@ -193,6 +193,14 @@ export class ProgrammingExerciseComponent extends ExerciseComponent implements O
modalRef.componentInstance.selectedProgrammingExercises = this.selectedProgrammingExercises;
}

/**
* Opens modal and executes a consistency check for the selected exercises
*/
checkConsistencies() {
const modalRef = this.modalService.open(ConsistencyCheckComponent, { keyboard: true, size: 'lg' });
modalRef.componentInstance.exercisesToCheck = this.selectedProgrammingExercises;
}

// ################## ONLY FOR LOCAL TESTING PURPOSE -- START ##################

/**
Expand Down
Loading

0 comments on commit b50de55

Please sign in to comment.