Skip to content

Commit

Permalink
Merge branch 'develop' into feature/programming-exercises/ts-template
Browse files Browse the repository at this point in the history
  • Loading branch information
magaupp committed Oct 12, 2024
2 parents f209a54 + d801ef8 commit 774e0b0
Show file tree
Hide file tree
Showing 282 changed files with 5,345 additions and 1,672 deletions.
8 changes: 4 additions & 4 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,10 @@ module.exports = {
coverageThreshold: {
global: {
// TODO: in the future, the following values should increase to at least 90%
statements: 87.44,
branches: 73.74,
functions: 82.10,
lines: 87.49,
statements: 87.39,
branches: 73.60,
functions: 81.97,
lines: 87.45,
},
},
coverageReporters: ['clover', 'json', 'lcov', 'text-summary'],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,14 @@ SELECT COUNT(DISTINCT p)
*/
boolean existsByParticipationId(long participationId);

/**
* Checks if a result exists for the given submission ID.
*
* @param submissionId the ID of the submission to check.
* @return true if a result exists for the given submission ID, false otherwise.
*/
boolean existsBySubmissionId(long submissionId);

/**
* Returns true if there is at least one result for the given exercise.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public record BuildConfig(String buildScript, String dockerImage, String commitHashToBuild, String assignmentCommitHash, String testCommitHash, String branch,
ProgrammingLanguage programmingLanguage, ProjectType projectType, boolean scaEnabled, boolean sequentialTestRunsEnabled, boolean testwiseCoverageEnabled,
List<String> resultPaths) implements Serializable {
List<String> resultPaths, int timeoutSeconds, String assignmentCheckoutPath, String testCheckoutPath, String solutionCheckoutPath) implements Serializable {

@Override
public String dockerImage() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
Expand Down Expand Up @@ -275,11 +276,22 @@ public String getIDOfRunningContainer(String containerName) {
* @param auxiliaryRepositoriesPaths An array of paths for auxiliary repositories to be included in the build process.
* @param auxiliaryRepositoryCheckoutDirectories An array of directory names within the container where each auxiliary repository should be checked out.
* @param programmingLanguage The programming language of the repositories, which influences directory naming conventions.
* @param assignmentCheckoutPath The directory within the container where the assignment repository should be checked out; can be null if not applicable,
* default would be used.
* @param testCheckoutPath The directory within the container where the test repository should be checked out; can be null if not applicable, default
* would be used.
* @param solutionCheckoutPath The directory within the container where the solution repository should be checked out; can be null if not applicable, default
* would be used.
*/
public void populateBuildJobContainer(String buildJobContainerId, Path assignmentRepositoryPath, Path testRepositoryPath, Path solutionRepositoryPath,
Path[] auxiliaryRepositoriesPaths, String[] auxiliaryRepositoryCheckoutDirectories, ProgrammingLanguage programmingLanguage) {
String testCheckoutPath = RepositoryCheckoutPath.TEST.forProgrammingLanguage(programmingLanguage);
String assignmentCheckoutPath = RepositoryCheckoutPath.ASSIGNMENT.forProgrammingLanguage(programmingLanguage);
Path[] auxiliaryRepositoriesPaths, String[] auxiliaryRepositoryCheckoutDirectories, ProgrammingLanguage programmingLanguage, String assignmentCheckoutPath,
String testCheckoutPath, String solutionCheckoutPath) {

assignmentCheckoutPath = (!StringUtils.isBlank(assignmentCheckoutPath)) ? assignmentCheckoutPath
: RepositoryCheckoutPath.ASSIGNMENT.forProgrammingLanguage(programmingLanguage);

String defaultTestCheckoutPath = RepositoryCheckoutPath.TEST.forProgrammingLanguage(programmingLanguage);
testCheckoutPath = (!StringUtils.isBlank(defaultTestCheckoutPath) && !StringUtils.isBlank(testCheckoutPath)) ? testCheckoutPath : defaultTestCheckoutPath;

// Make sure to create the working directory in case it does not exist.
// In case the test checkout path is the working directory, we only create up to the parent, as the working directory is created below.
Expand All @@ -292,7 +304,8 @@ public void populateBuildJobContainer(String buildJobContainerId, Path assignmen
// Copy the assignment repository to the container and move it to the assignment checkout path
addAndPrepareDirectory(buildJobContainerId, assignmentRepositoryPath, LOCALCI_WORKING_DIRECTORY + "/testing-dir/" + assignmentCheckoutPath);
if (solutionRepositoryPath != null) {
String solutionCheckoutPath = RepositoryCheckoutPath.SOLUTION.forProgrammingLanguage(programmingLanguage);
solutionCheckoutPath = (!StringUtils.isBlank(solutionCheckoutPath)) ? solutionCheckoutPath
: RepositoryCheckoutPath.SOLUTION.forProgrammingLanguage(programmingLanguage);
addAndPrepareDirectory(buildJobContainerId, solutionRepositoryPath, LOCALCI_WORKING_DIRECTORY + "/testing-dir/" + solutionCheckoutPath);
}
for (int i = 0; i < auxiliaryRepositoriesPaths.length; i++) {
Expand All @@ -309,6 +322,7 @@ private void createScriptFile(String buildJobContainerId) {

private void addAndPrepareDirectory(String containerId, Path repositoryPath, String newDirectoryName) {
copyToContainer(repositoryPath.toString(), containerId);
addDirectory(containerId, getParentFolderPath(newDirectoryName), true);
renameDirectoryOrFile(containerId, LOCALCI_WORKING_DIRECTORY + "/" + repositoryPath.getFileName().toString(), newDirectoryName);
}

Expand Down Expand Up @@ -428,4 +442,9 @@ private Container getContainerForName(String containerName) {
List<Container> containers = dockerClient.listContainersCmd().withShowAll(true).exec();
return containers.stream().filter(container -> container.getNames()[0].equals("/" + containerName)).findFirst().orElse(null);
}

private String getParentFolderPath(String path) {
Path parentPath = Paths.get(path).normalize().getParent();
return parentPath != null ? parentPath.toString() : "";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,8 @@ private BuildResult runScriptAndParseResults(BuildJobQueueItem buildJob, String
buildLogsMap.appendBuildLogEntry(buildJob.id(), msg);
log.debug(msg);
buildJobContainerService.populateBuildJobContainer(containerId, assignmentRepositoryPath, testsRepositoryPath, solutionRepositoryPath, auxiliaryRepositoriesPaths,
buildJob.repositoryInfo().auxiliaryRepositoryCheckoutDirectories(), buildJob.buildConfig().programmingLanguage());
buildJob.repositoryInfo().auxiliaryRepositoryCheckoutDirectories(), buildJob.buildConfig().programmingLanguage(), buildJob.buildConfig().assignmentCheckoutPath(),
buildJob.buildConfig().testCheckoutPath(), buildJob.buildConfig().solutionCheckoutPath());

msg = "~~~~~~~~~~~~~~~~~~~~ Executing Build Script for Build job " + buildJob.id() + " ~~~~~~~~~~~~~~~~~~~~";
buildLogsMap.appendBuildLogEntry(buildJob.id(), msg);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public class BuildJobManagementService {

private final ReentrantLock lock = new ReentrantLock();

@Value("${artemis.continuous-integration.timeout-seconds:240}")
@Value("${artemis.continuous-integration.timeout-seconds:120}")
private int timeoutSeconds;

@Value("${artemis.continuous-integration.asynchronous:true}")
Expand Down Expand Up @@ -149,9 +149,17 @@ public CompletableFuture<BuildResult> executeBuildJob(BuildJobQueueItem buildJob
lock.unlock();
}

int buildJobTimeoutSeconds;
if (buildJobItem.buildConfig().timeoutSeconds() != 0 && buildJobItem.buildConfig().timeoutSeconds() < this.timeoutSeconds) {
buildJobTimeoutSeconds = buildJobItem.buildConfig().timeoutSeconds();
}
else {
buildJobTimeoutSeconds = this.timeoutSeconds;
}

CompletableFuture<BuildResult> futureResult = createCompletableFuture(() -> {
try {
return future.get(timeoutSeconds, TimeUnit.SECONDS);
return future.get(buildJobTimeoutSeconds, TimeUnit.SECONDS);
}
catch (Exception e) {
// RejectedExecutionException is thrown if the queue size limit (defined in "artemis.continuous-integration.queue-size-limit") is reached.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,10 @@ public void addTag(String tag) {
this.tags.add(tag);
}

public void setCourse(Course course) {
this.course = course;
}

public Conversation getConversation() {
return conversation;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,6 @@ default AnswerPost findAnswerPostByIdElseThrow(Long answerPostId) {
default AnswerPost findAnswerMessageByIdElseThrow(Long answerPostId) {
return getValueElseThrow(findById(answerPostId).filter(answerPost -> answerPost.getPost().getConversation() != null), answerPostId);
}

long countAnswerPostsByPostIdIn(List<Long> postIds);
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,8 @@ default Post findPostByIdElseThrow(Long postId) throws EntityNotFoundException {
default Post findPostOrMessagePostByIdElseThrow(Long postId) throws EntityNotFoundException {
return getValueElseThrow(findById(postId), postId);
}

List<Post> findAllByConversationId(Long conversationId);

List<Post> findAllByCourseId(Long courseId);
}
14 changes: 14 additions & 0 deletions src/main/java/de/tum/cit/aet/artemis/core/config/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ public final class Constants {
// Used to cut off CI specific path segments when receiving static code analysis reports
public static final String ASSIGNMENT_DIRECTORY = "/" + ASSIGNMENT_REPO_NAME + "/";

public static final String TEST_WORKING_DIRECTORY = "test";

// Used as a value for <sourceDirectory> for the Java template pom.xml
public static final String STUDENT_WORKING_DIRECTORY = ASSIGNMENT_DIRECTORY + "src";

Expand Down Expand Up @@ -390,6 +392,18 @@ public final class Constants {
*/
public static final int MIN_SCORE_ORANGE = 40;

public static final String ASSIGNMENT_REPO_PLACEHOLDER = "${studentWorkingDirectory}";

public static final String TEST_REPO_PLACEHOLDER = "${testWorkingDirectory}";

public static final String SOLUTION_REPO_PLACEHOLDER = "${solutionWorkingDirectory}";

public static final String ASSIGNMENT_REPO_PARENT_PLACEHOLDER = "${studentParentWorkingDirectoryName}";

public static final String ASSIGNMENT_REPO_PLACEHOLDER_NO_SLASH = "${studentWorkingDirectoryNoSlash}";

public static final Pattern ALLOWED_CHECKOUT_DIRECTORY = Pattern.compile("[\\w-]+(/[\\w-]+)*$");

private Constants() {
}
}
5 changes: 5 additions & 0 deletions src/main/java/de/tum/cit/aet/artemis/core/domain/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -561,4 +561,9 @@ public void hasAcceptedIrisElseThrow() {
public String getSshPublicKey() {
return sshPublicKey;
}

@Nullable
public @Size(max = 100) String getSshPublicKeyHash() {
return sshPublicKeyHash;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package de.tum.cit.aet.artemis.core.dto;

import com.fasterxml.jackson.annotation.JsonInclude;

@JsonInclude(JsonInclude.Include.NON_EMPTY)
public record CourseDeletionSummaryDTO(long numberOfBuilds, long numberOfCommunicationPosts, long numberOfAnswerPosts) {
}
10 changes: 10 additions & 0 deletions src/main/java/de/tum/cit/aet/artemis/core/dto/UserDTO.java
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ public class UserDTO extends AuditingEntityDTO {

private String sshPublicKey;

private String sshKeyHash;

private ZonedDateTime irisAccepted;

public UserDTO() {
Expand Down Expand Up @@ -291,4 +293,12 @@ public ZonedDateTime getIrisAccepted() {
public void setIrisAccepted(ZonedDateTime irisAccepted) {
this.irisAccepted = irisAccepted;
}

public String getSshKeyHash() {
return sshKeyHash;
}

public void setSshKeyHash(String sshKeyHash) {
this.sshKeyHash = sshKeyHash;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,16 +58,20 @@
import de.tum.cit.aet.artemis.atlas.repository.PrerequisiteRepository;
import de.tum.cit.aet.artemis.atlas.service.learningpath.LearningPathService;
import de.tum.cit.aet.artemis.communication.domain.NotificationType;
import de.tum.cit.aet.artemis.communication.domain.Post;
import de.tum.cit.aet.artemis.communication.domain.notification.GroupNotification;
import de.tum.cit.aet.artemis.communication.repository.AnswerPostRepository;
import de.tum.cit.aet.artemis.communication.repository.FaqRepository;
import de.tum.cit.aet.artemis.communication.repository.GroupNotificationRepository;
import de.tum.cit.aet.artemis.communication.repository.PostRepository;
import de.tum.cit.aet.artemis.communication.repository.conversation.ConversationRepository;
import de.tum.cit.aet.artemis.communication.service.notifications.GroupNotificationService;
import de.tum.cit.aet.artemis.core.config.Constants;
import de.tum.cit.aet.artemis.core.domain.Course;
import de.tum.cit.aet.artemis.core.domain.DomainObject;
import de.tum.cit.aet.artemis.core.domain.User;
import de.tum.cit.aet.artemis.core.dto.CourseContentCount;
import de.tum.cit.aet.artemis.core.dto.CourseDeletionSummaryDTO;
import de.tum.cit.aet.artemis.core.dto.CourseManagementDetailViewDTO;
import de.tum.cit.aet.artemis.core.dto.DueDateStat;
import de.tum.cit.aet.artemis.core.dto.SearchResultPageDTO;
Expand Down Expand Up @@ -104,6 +108,7 @@
import de.tum.cit.aet.artemis.plagiarism.domain.PlagiarismCase;
import de.tum.cit.aet.artemis.plagiarism.repository.PlagiarismCaseRepository;
import de.tum.cit.aet.artemis.programming.domain.ProgrammingExercise;
import de.tum.cit.aet.artemis.programming.repository.BuildJobRepository;
import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseRepository;
import de.tum.cit.aet.artemis.tutorialgroup.repository.TutorialGroupNotificationRepository;
import de.tum.cit.aet.artemis.tutorialgroup.repository.TutorialGroupRepository;
Expand Down Expand Up @@ -201,6 +206,12 @@ public class CourseService {

private final TutorialGroupNotificationRepository tutorialGroupNotificationRepository;

private final PostRepository postRepository;

private final AnswerPostRepository answerPostRepository;

private final BuildJobRepository buildJobRepository;

public CourseService(CourseRepository courseRepository, ExerciseService exerciseService, ExerciseDeletionService exerciseDeletionService,
AuthorizationCheckService authCheckService, UserRepository userRepository, LectureService lectureService, GroupNotificationRepository groupNotificationRepository,
ExerciseGroupRepository exerciseGroupRepository, AuditEventRepository auditEventRepository, UserService userService, ExamDeletionService examDeletionService,
Expand All @@ -213,7 +224,8 @@ public CourseService(CourseRepository courseRepository, ExerciseService exercise
TutorialGroupRepository tutorialGroupRepository, PlagiarismCaseRepository plagiarismCaseRepository, ConversationRepository conversationRepository,
LearningPathService learningPathService, Optional<IrisSettingsService> irisSettingsService, LectureRepository lectureRepository,
TutorialGroupNotificationRepository tutorialGroupNotificationRepository, TutorialGroupChannelManagementService tutorialGroupChannelManagementService,
PrerequisiteRepository prerequisiteRepository, CompetencyRelationRepository competencyRelationRepository, FaqRepository faqRepository) {
PrerequisiteRepository prerequisiteRepository, CompetencyRelationRepository competencyRelationRepository, PostRepository postRepository,
AnswerPostRepository answerPostRepository, BuildJobRepository buildJobRepository, FaqRepository faqRepository) {
this.courseRepository = courseRepository;
this.exerciseService = exerciseService;
this.exerciseDeletionService = exerciseDeletionService;
Expand Down Expand Up @@ -253,6 +265,9 @@ public CourseService(CourseRepository courseRepository, ExerciseService exercise
this.tutorialGroupChannelManagementService = tutorialGroupChannelManagementService;
this.prerequisiteRepository = prerequisiteRepository;
this.competencyRelationRepository = competencyRelationRepository;
this.buildJobRepository = buildJobRepository;
this.postRepository = postRepository;
this.answerPostRepository = answerPostRepository;
this.faqRepository = faqRepository;
}

Expand Down Expand Up @@ -444,6 +459,22 @@ public Set<Course> findAllOnlineCoursesForPlatformForUser(String registrationId,
.collect(Collectors.toSet());
}

/**
* Get the course deletion summary for the given course.
*
* @param course the course for which to get the deletion summary
* @return the course deletion summary
*/
public CourseDeletionSummaryDTO getDeletionSummary(Course course) {
List<Long> programmingExerciseIds = course.getExercises().stream().map(Exercise::getId).toList();
long numberOfBuilds = buildJobRepository.countBuildJobsByExerciseIds(programmingExerciseIds);

List<Post> posts = postRepository.findAllByCourseId(course.getId());
long numberOfCommunicationPosts = posts.size();
long numberOfAnswerPosts = answerPostRepository.countAnswerPostsByPostIdIn(posts.stream().map(Post::getId).toList());
return new CourseDeletionSummaryDTO(numberOfBuilds, numberOfCommunicationPosts, numberOfAnswerPosts);
}

/**
* Deletes all elements associated with the course including:
* <ul>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import de.tum.cit.aet.artemis.core.config.Constants;
import de.tum.cit.aet.artemis.core.domain.Course;
import de.tum.cit.aet.artemis.core.domain.User;
import de.tum.cit.aet.artemis.core.dto.CourseDeletionSummaryDTO;
import de.tum.cit.aet.artemis.core.exception.BadRequestAlertException;
import de.tum.cit.aet.artemis.core.repository.CourseRepository;
import de.tum.cit.aet.artemis.core.repository.UserRepository;
Expand Down Expand Up @@ -181,6 +182,20 @@ public ResponseEntity<Void> deleteCourse(@PathVariable long courseId) {
return ResponseEntity.ok().headers(HeaderUtil.createEntityDeletionAlert(applicationName, true, Course.ENTITY_NAME, course.getTitle())).build();
}

/**
* GET /courses/:courseId/deletion-summary : get the deletion summary for the course with the given id.
*
* @param courseId the id of the course
* @return the ResponseEntity with status 200 (OK) and the deletion summary in the body
*/
@GetMapping("courses/{courseId}/deletion-summary")
public ResponseEntity<CourseDeletionSummaryDTO> getDeletionSummary(@PathVariable long courseId) {
log.debug("REST request to get deletion summary course: {}", courseId);
final Course course = courseRepository.findByIdWithEagerExercisesElseThrow(courseId);

return ResponseEntity.ok().body(courseService.getDeletionSummary(course));
}

/**
* Creates a default channel with the given name and adds all students, tutors and instructors as participants.
*
Expand Down
Loading

0 comments on commit 774e0b0

Please sign in to comment.