From af9a52b41cbf5d8a857b304dcf5ad261c6e757e5 Mon Sep 17 00:00:00 2001 From: Stephan Krusche Date: Wed, 3 Jan 2024 13:09:57 +0100 Subject: [PATCH 01/25] Development: Improve server code related to local version control --- .../config/localvcci/ArtemisGitServlet.java | 8 +-- .../de/tum/in/www1/artemis/domain/Commit.java | 51 +---------------- .../bitbucket/BitbucketService.java | 19 +++---- .../connectors/gitlab/GitLabService.java | 12 ++-- .../LocalCISharedBuildJobQueueService.java | 2 +- .../dto/LocalCIBuildAgentInformation.java | 56 +------------------ .../localci/dto/LocalCIBuildJobQueueItem.java | 55 ++++-------------- .../localci/dto/LocalCIBuildResult.java | 6 +- .../localvc/LocalVCServletService.java | 30 +++++----- .../ProgrammingSubmissionService.java | 20 +++---- .../PublicProgrammingSubmissionResource.java | 2 +- .../GitlabServiceTest.java | 30 +++++----- ...dResultBitbucketBambooIntegrationTest.java | 3 +- .../ProgrammingSubmissionIntegrationTest.java | 22 ++++---- 14 files changed, 83 insertions(+), 233 deletions(-) diff --git a/src/main/java/de/tum/in/www1/artemis/config/localvcci/ArtemisGitServlet.java b/src/main/java/de/tum/in/www1/artemis/config/localvcci/ArtemisGitServlet.java index 24621314adb7..6bf965160593 100644 --- a/src/main/java/de/tum/in/www1/artemis/config/localvcci/ArtemisGitServlet.java +++ b/src/main/java/de/tum/in/www1/artemis/config/localvcci/ArtemisGitServlet.java @@ -20,8 +20,8 @@ public class ArtemisGitServlet extends GitServlet { * @param localVCServletService the service for authenticating and authorizing users and retrieving the repository from disk */ public ArtemisGitServlet(LocalVCServletService localVCServletService) { - this.setRepositoryResolver((req, name) -> { - // req – the current request, may be used to inspect session state including cookies or user authentication. + this.setRepositoryResolver((request, name) -> { + // request – the current request, may be used to inspect session state including cookies or user authentication. // name – name of the repository, as parsed out of the URL (everything after /git/). // Return the opened repository instance. @@ -32,8 +32,8 @@ public ArtemisGitServlet(LocalVCServletService localVCServletService) { this.addUploadPackFilter(new LocalVCFetchFilter(localVCServletService)); this.addReceivePackFilter(new LocalVCPushFilter(localVCServletService)); - this.setReceivePackFactory((req, db) -> { - ReceivePack receivePack = new ReceivePack(db); + this.setReceivePackFactory((request, repository) -> { + ReceivePack receivePack = new ReceivePack(repository); // Add a hook that prevents illegal actions on push (delete branch, rename branch, force push). receivePack.setPreReceiveHook(new LocalVCPrePushHook()); // Add a hook that triggers the creation of a new submission after the push went through successfully. diff --git a/src/main/java/de/tum/in/www1/artemis/domain/Commit.java b/src/main/java/de/tum/in/www1/artemis/domain/Commit.java index c9a4204a6e84..03924f97e6be 100644 --- a/src/main/java/de/tum/in/www1/artemis/domain/Commit.java +++ b/src/main/java/de/tum/in/www1/artemis/domain/Commit.java @@ -1,54 +1,7 @@ package de.tum.in.www1.artemis.domain; -public class Commit { +import javax.annotation.Nullable; - private String commitHash; +public record Commit(@Nullable String commitHash, @Nullable String authorName, @Nullable String message, @Nullable String authorEmail, @Nullable String branch) { - private String authorName; - - private String message; - - private String authorEmail; - - private String branch; - - public String getMessage() { - return message; - } - - public void setMessage(String message) { - this.message = message; - } - - public String getCommitHash() { - return commitHash; - } - - public void setCommitHash(String commitHash) { - this.commitHash = commitHash; - } - - public String getAuthorName() { - return authorName; - } - - public void setAuthorName(String authorName) { - this.authorName = authorName; - } - - public String getAuthorEmail() { - return authorEmail; - } - - public void setAuthorEmail(String authorEmail) { - this.authorEmail = authorEmail; - } - - public String getBranch() { - return branch; - } - - public void setBranch(String branch) { - this.branch = branch; - } } diff --git a/src/main/java/de/tum/in/www1/artemis/service/connectors/bitbucket/BitbucketService.java b/src/main/java/de/tum/in/www1/artemis/service/connectors/bitbucket/BitbucketService.java index 402c652c8cb7..2071fd593c6a 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/connectors/bitbucket/BitbucketService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/connectors/bitbucket/BitbucketService.java @@ -8,10 +8,7 @@ import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import javax.annotation.Nullable; import javax.validation.constraints.NotNull; @@ -762,37 +759,35 @@ public Commit getLastCommitDetails(Object requestBody) throws BitbucketException // {"eventKey":"...","date":"...","actor":{...},"repository":{...},"changes":[{"ref":{...},"refId":"refs/heads/main", // "fromHash":"5626436a443eb898a5c5f74b6352f26ea2b7c84e","toHash":"662868d5e16406d1dd4dcfa8ac6c46ee3d677924","type":"UPDATE"}]} // we are interested in the toHash - Commit commit = new Commit(); try { // TODO: use a DTO (e.g. something similar to CommitDTO) final var commitData = new ObjectMapper().convertValue(requestBody, JsonNode.class); var lastChange = commitData.get("changes").get(0); var ref = lastChange.get("ref"); + String branch = null; if (ref != null) { - var branch = ref.get("displayId").asText(); - commit.setBranch(branch); + branch = ref.get("displayId").asText(); } var hash = lastChange.get("toHash").asText(); - commit.setCommitHash(hash); var actor = commitData.get("actor"); String name = actor.get("name").asText(); String email = actor.get("emailAddress").asText(); + String message = null; if (artemisGitName.equalsIgnoreCase(name)) { final var commitInfo = fetchCommitInfo(commitData, hash); if (commitInfo != null) { - commit.setMessage(commitInfo.get("message").asText()); + message = commitInfo.get("message").asText(); name = commitInfo.get("author").get("name").asText(); email = commitInfo.get("author").get("emailAddress").asText(); } } - commit.setAuthorName(name); - commit.setAuthorEmail(email); + return new Commit(hash, name, message, email, branch); } catch (Exception e) { // silently fail because this step is not absolutely necessary log.error("Error when getting hash of last commit. Able to continue.", e); } - return commit; + return new Commit(null, null, null, null, null); } @Override diff --git a/src/main/java/de/tum/in/www1/artemis/service/connectors/gitlab/GitLabService.java b/src/main/java/de/tum/in/www1/artemis/service/connectors/gitlab/GitLabService.java index e0932e2dd608..a704ace61d8b 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/connectors/gitlab/GitLabService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/connectors/gitlab/GitLabService.java @@ -312,25 +312,21 @@ public Boolean repositoryUrlIsValid(@Nullable VcsRepositoryUrl repositoryUrl) { @Override public Commit getLastCommitDetails(Object requestBody) throws VersionControlException { final var details = GitLabPushNotificationDTO.convert(requestBody); - final var commit = new Commit(); // Gitlab specifically provide the previous latest commit and the new latest commit after the given push event // Here we retrieve the hash of the new latest commit final var gitLabCommitHash = details.getNewHash(); - commit.setCommitHash(gitLabCommitHash); // Here we search for the commit details for the given commit hash // Technically these details should always be present but as this could change, we handle the edge case final var firstMatchingCommit = details.getCommits().stream().filter(com -> gitLabCommitHash.equals(com.getHash())).findFirst(); if (firstMatchingCommit.isPresent()) { // Fill commit with commit details final var gitLabCommit = firstMatchingCommit.get(); - commit.setMessage(gitLabCommit.getMessage()); - commit.setAuthorEmail(gitLabCommit.getAuthor().getEmail()); - commit.setAuthorName(gitLabCommit.getAuthor().getName()); final var ref = details.getRef().split("/"); - commit.setBranch(ref[ref.length - 1]); + var branch = ref[ref.length - 1]; + var author = gitLabCommit.getAuthor(); + return new Commit(gitLabCommitHash, author.getName(), gitLabCommit.getMessage(), author.getEmail(), branch); } - - return commit; + return new Commit(gitLabCommitHash, null, null, null, null); } @Override diff --git a/src/main/java/de/tum/in/www1/artemis/service/connectors/localci/LocalCISharedBuildJobQueueService.java b/src/main/java/de/tum/in/www1/artemis/service/connectors/localci/LocalCISharedBuildJobQueueService.java index 4c2e70d06dc9..2fe7505dfcea 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/connectors/localci/LocalCISharedBuildJobQueueService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/connectors/localci/LocalCISharedBuildJobQueueService.java @@ -62,7 +62,7 @@ public class LocalCISharedBuildJobQueueService { private final IMap buildAgentInformation; - private AtomicInteger localProcessingJobs = new AtomicInteger(0); + private final AtomicInteger localProcessingJobs = new AtomicInteger(0); /** * Lock to prevent multiple nodes from processing the same build job. diff --git a/src/main/java/de/tum/in/www1/artemis/service/connectors/localci/dto/LocalCIBuildAgentInformation.java b/src/main/java/de/tum/in/www1/artemis/service/connectors/localci/dto/LocalCIBuildAgentInformation.java index e380d55f4239..8fd5d8cdbb44 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/connectors/localci/dto/LocalCIBuildAgentInformation.java +++ b/src/main/java/de/tum/in/www1/artemis/service/connectors/localci/dto/LocalCIBuildAgentInformation.java @@ -4,61 +4,9 @@ import java.io.Serializable; import java.util.List; -public class LocalCIBuildAgentInformation implements Serializable { +public record LocalCIBuildAgentInformation(String name, int maxNumberOfConcurrentBuildJobs, int numberOfCurrentBuildJobs, List runningBuildJobs) + implements Serializable { @Serial private static final long serialVersionUID = 1L; - - private String name; - - private int maxNumberOfConcurrentBuildJobs; - - private int numberOfCurrentBuildJobs; - - private List runningBuildJobs; - - public LocalCIBuildAgentInformation(String name, int maxNumberOfConcurrentBuildJobs, int numberOfCurrentBuildJobs, List runningBuildJobs) { - this.name = name; - this.maxNumberOfConcurrentBuildJobs = maxNumberOfConcurrentBuildJobs; - this.numberOfCurrentBuildJobs = numberOfCurrentBuildJobs; - this.runningBuildJobs = runningBuildJobs; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public int getMaxNumberOfConcurrentBuildJobs() { - return maxNumberOfConcurrentBuildJobs; - } - - public void setMaxNumberOfConcurrentBuildJobs(int maxNumberOfConcurrentBuildJobs) { - this.maxNumberOfConcurrentBuildJobs = maxNumberOfConcurrentBuildJobs; - } - - public int getNumberOfCurrentBuildJobs() { - return numberOfCurrentBuildJobs; - } - - public void setNumberOfCurrentBuildJobs(int numberOfCurrentBuildJobs) { - this.numberOfCurrentBuildJobs = numberOfCurrentBuildJobs; - } - - public List getRunningBuildJobs() { - return runningBuildJobs; - } - - public void setRunningBuildJobs(List runningBuildJobs) { - this.runningBuildJobs = runningBuildJobs; - } - - @Override - public String toString() { - return "LocalCIBuildAgentInformation{" + "name='" + name + '\'' + ", maxNumberOfConcurrentBuildJobs=" + maxNumberOfConcurrentBuildJobs + ", numberOfCurrentBuildJobs=" - + numberOfCurrentBuildJobs + ", runningBuildJobs=" + runningBuildJobs + '}'; - } } diff --git a/src/main/java/de/tum/in/www1/artemis/service/connectors/localci/dto/LocalCIBuildJobQueueItem.java b/src/main/java/de/tum/in/www1/artemis/service/connectors/localci/dto/LocalCIBuildJobQueueItem.java index 936ef041cc5b..e44f67543d9e 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/connectors/localci/dto/LocalCIBuildJobQueueItem.java +++ b/src/main/java/de/tum/in/www1/artemis/service/connectors/localci/dto/LocalCIBuildJobQueueItem.java @@ -4,35 +4,36 @@ import java.io.Serializable; import java.time.ZonedDateTime; +// TODO: we should convert this into a record to make objects immutable public class LocalCIBuildJobQueueItem implements Serializable { @Serial private static final long serialVersionUID = 1L; - private long id; + private final long id; - private String name; + private final String name; private String buildAgentAddress; - private long participationId; + private final long participationId; - private String repositoryTypeOrUserName; + private final String repositoryTypeOrUserName; - private String commitHash; + private final String commitHash; - private ZonedDateTime submissionDate; + private final ZonedDateTime submissionDate; private int retryCount; private ZonedDateTime buildStartDate; // 1-5, 1 is highest priority - private int priority; + private final int priority; - private long courseId; + private final long courseId; - private boolean isPushToTestRepository; + private final boolean isPushToTestRepository; public LocalCIBuildJobQueueItem(String name, long participationId, String repositoryTypeOrUserName, String commitHash, ZonedDateTime submissionDate, int priority, long courseId, boolean isPushToTestRepository) { @@ -51,18 +52,10 @@ public long getId() { return id; } - public void setId(long id) { - this.id = id; - } - public String getName() { return name; } - public void setName(String name) { - this.name = name; - } - public String getBuildAgentAddress() { return buildAgentAddress; } @@ -75,34 +68,18 @@ public long getParticipationId() { return participationId; } - public void setParticipationId(long participationId) { - this.participationId = participationId; - } - public String getRepositoryTypeOrUserName() { return repositoryTypeOrUserName; } - public void setRepositoryTypeOrUserName(String repositoryTypeOrUserName) { - this.repositoryTypeOrUserName = repositoryTypeOrUserName; - } - public String getCommitHash() { return commitHash; } - public void setCommitHash(String commitHash) { - this.commitHash = commitHash; - } - public ZonedDateTime getSubmissionDate() { return submissionDate; } - public void setSubmissionDate(ZonedDateTime submissionDate) { - this.submissionDate = submissionDate; - } - public ZonedDateTime getBuildStartDate() { return buildStartDate; } @@ -115,10 +92,6 @@ public int getPriority() { return priority; } - public void setPriority(int priority) { - this.priority = priority; - } - public int getRetryCount() { return retryCount; } @@ -131,10 +104,6 @@ public long getCourseId() { return courseId; } - public void setCourseId(long courseId) { - this.courseId = courseId; - } - /** * When pushing to the test repository, build jobs are triggered for the template and solution repository. * However, getCommitHash() then returns the commit hash of the test repository. @@ -146,10 +115,6 @@ public boolean isPushToTestRepository() { return isPushToTestRepository; } - public void setPushToTestRepository(boolean isPushToTestRepository) { - this.isPushToTestRepository = isPushToTestRepository; - } - @Override public String toString() { return "LocalCIBuildJobQueueItem{" + "id='" + id + '\'' + ", name='" + name + '\'' + ", participationId=" + participationId + ", repositoryTypeOrUserName='" diff --git a/src/main/java/de/tum/in/www1/artemis/service/connectors/localci/dto/LocalCIBuildResult.java b/src/main/java/de/tum/in/www1/artemis/service/connectors/localci/dto/LocalCIBuildResult.java index 108cc6320aff..c3d431ce8023 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/connectors/localci/dto/LocalCIBuildResult.java +++ b/src/main/java/de/tum/in/www1/artemis/service/connectors/localci/dto/LocalCIBuildResult.java @@ -33,7 +33,7 @@ public class LocalCIBuildResult extends AbstractBuildResultNotificationDTO { private List buildLogEntries = new ArrayList<>(); - private List staticCodeAnalysisReports; + private final List staticCodeAnalysisReports; private boolean hasLogs = false; @@ -55,7 +55,7 @@ public ZonedDateTime getBuildRunDate() { @Override public Optional getCommitHashFromAssignmentRepo() { - if (assignmentRepoCommitHash.length() == 0) { + if (assignmentRepoCommitHash.isEmpty()) { return Optional.empty(); } return Optional.of(assignmentRepoCommitHash); @@ -63,7 +63,7 @@ public Optional getCommitHashFromAssignmentRepo() { @Override public Optional getCommitHashFromTestsRepo() { - if (testsRepoCommitHash.length() == 0) { + if (testsRepoCommitHash.isEmpty()) { return Optional.empty(); } return Optional.of(testsRepoCommitHash); diff --git a/src/main/java/de/tum/in/www1/artemis/service/connectors/localvc/LocalVCServletService.java b/src/main/java/de/tum/in/www1/artemis/service/connectors/localvc/LocalVCServletService.java index 0b318447bd3b..bd1fd437f59d 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/connectors/localvc/LocalVCServletService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/connectors/localvc/LocalVCServletService.java @@ -112,6 +112,8 @@ public LocalVCServletService(AuthenticationManagerBuilder authenticationManagerB } /** + * Resolves the repository for the given path by first trying to use a cached one. + * If the cache does not hit, it creates a JGit repository and opens the local repository. * * @param repositoryPath the path of the repository, as parsed out of the URL (everything after /git). * @return the opened repository instance. @@ -153,18 +155,17 @@ public Repository resolveRepository(String repositoryPath) throws RepositoryNotF /** * Determines whether a given request to access a local VC repository (either via fetch of push) is authenticated and authorized. * - * @param servletRequest The object containing all information about the incoming request. - * @param repositoryActionType Indicates whether the method should authenticate a fetch or a push request. For a push request, additional checks are conducted. + * @param request The object containing all information about the incoming request. + * @param repositoryAction Indicates whether the method should authenticate a fetch or a push request. For a push request, additional checks are conducted. * @throws LocalVCAuthException If the user authentication fails or the user is not authorized to access a certain repository. * @throws LocalVCForbiddenException If the user is not allowed to access the repository, e.g. because offline IDE usage is not allowed or the due date has passed. * @throws LocalVCInternalException If an internal error occurs, e.g. because the LocalVCRepositoryUrl could not be created. */ - public void authenticateAndAuthorizeGitRequest(HttpServletRequest servletRequest, RepositoryActionType repositoryActionType) - throws LocalVCAuthException, LocalVCForbiddenException { + public void authenticateAndAuthorizeGitRequest(HttpServletRequest request, RepositoryActionType repositoryAction) throws LocalVCAuthException, LocalVCForbiddenException { long timeNanoStart = System.nanoTime(); - User user = authenticateUser(servletRequest.getHeader(LocalVCServletService.AUTHORIZATION_HEADER)); + User user = authenticateUser(request.getHeader(LocalVCServletService.AUTHORIZATION_HEADER)); // Optimization. // For each git command (i.e. 'git fetch' or 'git push'), the git client sends three requests. @@ -172,11 +173,11 @@ public void authenticateAndAuthorizeGitRequest(HttpServletRequest servletRequest // URL]/git-upload-pack' (for fetch). // The following checks will only be conducted for the second request, so we do not have to access the database too often. // The first request does not contain credentials and will thus already be blocked by the 'authenticateUser' method above. - if (!servletRequest.getRequestURI().endsWith("/info/refs")) { + if (!request.getRequestURI().endsWith("/info/refs")) { return; } - LocalVCRepositoryUrl localVCRepositoryUrl = new LocalVCRepositoryUrl(servletRequest.getRequestURL().toString().replace("/info/refs", ""), localVCBaseUrl); + LocalVCRepositoryUrl localVCRepositoryUrl = new LocalVCRepositoryUrl(request.getRequestURL().toString().replace("/info/refs", ""), localVCBaseUrl); String projectKey = localVCRepositoryUrl.getProjectKey(); String repositoryTypeOrUserName = localVCRepositoryUrl.getRepositoryTypeOrUserName(); @@ -195,7 +196,7 @@ public void authenticateAndAuthorizeGitRequest(HttpServletRequest servletRequest throw new LocalVCForbiddenException(); } - authorizeUser(repositoryTypeOrUserName, user, exercise, repositoryActionType, localVCRepositoryUrl.isPracticeRepository()); + authorizeUser(repositoryTypeOrUserName, user, exercise, repositoryAction, localVCRepositoryUrl.isPracticeRepository()); log.info("Authorizing user {} for repository {} took {}", user.getLogin(), localVCRepositoryUrl, TimeLogUtil.formatDurationFrom(timeNanoStart)); } @@ -214,7 +215,7 @@ private User authenticateUser(String authorizationHeader) throws LocalVCAuthExce try { SecurityUtils.checkUsernameAndPasswordValidity(username, password); - // Try to authenticate the user. + // Try to authenticate the user based on the configured options, this can include sending the data to an external system (e.g. LDAP) or using internal authentication. UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password); authenticationManagerBuilder.getObject().authenticate(authenticationToken); } @@ -431,6 +432,7 @@ private ProgrammingSubmission getProgrammingSubmission(ProgrammingExercise exerc } /** + * TODO: this could be done asynchronously to shorten the duration of the push operation * Process a new push to a student's repository or to the template or solution repository of the exercise. * * @param participation the participation for which the push was made @@ -480,13 +482,7 @@ private Commit extractCommitInfo(String commitHash, Repository repository) throw throw new VersionControlException("Something went wrong retrieving the revCommit or the branch."); } - Commit commit = new Commit(); - commit.setCommitHash(commitHash); - commit.setAuthorName(revCommit.getAuthorIdent().getName()); - commit.setAuthorEmail(revCommit.getAuthorIdent().getEmailAddress()); - commit.setBranch(branch); - commit.setMessage(revCommit.getFullMessage()); - - return commit; + var author = revCommit.getAuthorIdent(); + return new Commit(commitHash, author.getName(), revCommit.getFullMessage(), author.getEmailAddress(), branch); } } diff --git a/src/main/java/de/tum/in/www1/artemis/service/programming/ProgrammingSubmissionService.java b/src/main/java/de/tum/in/www1/artemis/service/programming/ProgrammingSubmissionService.java index 0586a54cfa05..93607ea81b1c 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/programming/ProgrammingSubmissionService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/programming/ProgrammingSubmissionService.java @@ -122,8 +122,7 @@ public ProgrammingSubmission processNewProgrammingSubmission(ProgrammingExercise // we can find this out by looking into the requestBody, e.g. changes=[{ref={id=refs/heads/BitbucketStationSupplies, displayId=BitbucketStationSupplies, type=BRANCH} // if the branch is different from main, throw an IllegalArgumentException, but make sure the REST call still returns 200 to Bitbucket commit = versionControl.getLastCommitDetails(requestBody); - log.info("NotifyPush invoked due to the commit {} by {} with {} in branch {}", commit.getCommitHash(), commit.getAuthorName(), commit.getAuthorEmail(), - commit.getBranch()); + log.info("NotifyPush invoked due to the commit {} by {} with {} in branch {}", commit.commitHash(), commit.authorName(), commit.authorEmail(), commit.branch()); } catch (Exception ex) { log.error("Commit could not be parsed for submission from participation {}", participation, ex); @@ -131,13 +130,12 @@ public ProgrammingSubmission processNewProgrammingSubmission(ProgrammingExercise } String branch = versionControl.getOrRetrieveBranchOfParticipation(participation); - if (commit.getBranch() != null && !commit.getBranch().equalsIgnoreCase(branch)) { + if (commit.branch() != null && !commit.branch().equalsIgnoreCase(branch)) { // if the commit was made in a branch different from the default, ignore this throw new VersionControlException( - "Submission for participation id " + participation.getId() + " in branch " + commit.getBranch() + " will be ignored! Only the default branch is considered"); + "Submission for participation id " + participation.getId() + " in branch " + commit.branch() + " will be ignored! Only the default branch is considered"); } - if (artemisGitName.equalsIgnoreCase(commit.getAuthorName()) && artemisGitEmail.equalsIgnoreCase(commit.getAuthorEmail()) - && SETUP_COMMIT_MESSAGE.equals(commit.getMessage())) { + if (artemisGitName.equalsIgnoreCase(commit.authorName()) && artemisGitEmail.equalsIgnoreCase(commit.authorEmail()) && SETUP_COMMIT_MESSAGE.equals(commit.message())) { // if the commit was made by Artemis and the message is "Setup" (this means it is an empty setup commit), we ignore this as well and do not create a submission! throw new IllegalStateException("Submission for participation id " + participation.getId() + " based on an empty setup commit by Artemis will be ignored!"); } @@ -155,7 +153,7 @@ public ProgrammingSubmission processNewProgrammingSubmission(ProgrammingExercise // TODO: there might be cases in which Artemis should NOT trigger the build try { - continuousIntegrationTriggerService.orElseThrow().triggerBuild(participation, commit.getCommitHash(), false); + continuousIntegrationTriggerService.orElseThrow().triggerBuild(participation, commit.commitHash(), false); } catch (ContinuousIntegrationException ex) { // TODO: This case is currently not handled. The correct handling would be creating the submission and informing the user that the build trigger failed. @@ -163,14 +161,14 @@ public ProgrammingSubmission processNewProgrammingSubmission(ProgrammingExercise // There can't be two submissions for the same participation and commitHash! ProgrammingSubmission programmingSubmission = programmingSubmissionRepository.findFirstByParticipationIdAndCommitHashOrderByIdDesc(participation.getId(), - commit.getCommitHash()); + commit.commitHash()); if (programmingSubmission != null) { - throw new IllegalStateException("Submission for participation id " + participation.getId() + " and commitHash " + commit.getCommitHash() + " already exists!"); + throw new IllegalStateException("Submission for participation id " + participation.getId() + " and commitHash " + commit.commitHash() + " already exists!"); } programmingSubmission = new ProgrammingSubmission(); - programmingSubmission.setCommitHash(commit.getCommitHash()); - log.info("Create new programmingSubmission with commitHash: {} for participation {}", commit.getCommitHash(), participation.getId()); + programmingSubmission.setCommitHash(commit.commitHash()); + log.info("Create new programmingSubmission with commitHash: {} for participation {}", commit.commitHash(), participation.getId()); programmingSubmission.setSubmitted(true); programmingSubmission.setSubmissionDate(submissionDate); diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/open/PublicProgrammingSubmissionResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/open/PublicProgrammingSubmissionResource.java index 43f47ed95b31..553bf0ece28b 100644 --- a/src/main/java/de/tum/in/www1/artemis/web/rest/open/PublicProgrammingSubmissionResource.java +++ b/src/main/java/de/tum/in/www1/artemis/web/rest/open/PublicProgrammingSubmissionResource.java @@ -147,7 +147,7 @@ public ResponseEntity testCaseChanged(@PathVariable Long exerciseId, @Requ String lastCommitHash = null; try { Commit commit = versionControlService.orElseThrow().getLastCommitDetails(requestBody); - lastCommitHash = commit.getCommitHash(); + lastCommitHash = commit.commitHash(); log.info("create new programmingSubmission with commitHash: {} for exercise {}", lastCommitHash, exerciseId); } catch (Exception ex) { diff --git a/src/test/java/de/tum/in/www1/artemis/exercise/programmingexercise/GitlabServiceTest.java b/src/test/java/de/tum/in/www1/artemis/exercise/programmingexercise/GitlabServiceTest.java index 5e58a4df8961..c3819cc29351 100644 --- a/src/test/java/de/tum/in/www1/artemis/exercise/programmingexercise/GitlabServiceTest.java +++ b/src/test/java/de/tum/in/www1/artemis/exercise/programmingexercise/GitlabServiceTest.java @@ -152,11 +152,11 @@ void testGetLastCommitDetails() throws JsonProcessingException { String latestCommitHash = "11028e4104243d8cbae9175f2bc938cb8c2d7924"; Object body = new ObjectMapper().readValue(GITLAB_PUSH_EVENT_REQUEST, Object.class); Commit commit = versionControlService.getLastCommitDetails(body); - assertThat(commit.getCommitHash()).isEqualTo(latestCommitHash); - assertThat(commit.getBranch()).isNotNull(); - assertThat(commit.getMessage()).isNotNull(); - assertThat(commit.getAuthorEmail()).isNotNull(); - assertThat(commit.getAuthorName()).isNotNull(); + assertThat(commit.commitHash()).isEqualTo(latestCommitHash); + assertThat(commit.branch()).isNotNull(); + assertThat(commit.message()).isNotNull(); + assertThat(commit.authorEmail()).isNotNull(); + assertThat(commit.authorName()).isNotNull(); } @Test @@ -164,11 +164,11 @@ void testGetLastCommitDetailsWrongCommitOrder() throws JsonProcessingException { String latestCommitHash = "11028e4104243d8cbae9175f2bc938cb8c2d7924"; Object body = new ObjectMapper().readValue(GITLAB_PUSH_EVENT_REQUEST_WRONG_COMMIT_ORDER, Object.class); Commit commit = versionControlService.getLastCommitDetails(body); - assertThat(commit.getCommitHash()).isEqualTo(latestCommitHash); - assertThat(commit.getBranch()).isNotNull(); - assertThat(commit.getMessage()).isNotNull(); - assertThat(commit.getAuthorEmail()).isNotNull(); - assertThat(commit.getAuthorName()).isNotNull(); + assertThat(commit.commitHash()).isEqualTo(latestCommitHash); + assertThat(commit.branch()).isNotNull(); + assertThat(commit.message()).isNotNull(); + assertThat(commit.authorEmail()).isNotNull(); + assertThat(commit.authorName()).isNotNull(); } @Test @@ -176,10 +176,10 @@ void testGetLastCommitDetailsWithoutCommits() throws JsonProcessingException { String latestCommitHash = "11028e4104243d8cbae9175f2bc938cb8c2d7924"; Object body = new ObjectMapper().readValue(GITLAB_PUSH_EVENT_REQUEST_WITHOUT_COMMIT, Object.class); Commit commit = versionControlService.getLastCommitDetails(body); - assertThat(commit.getCommitHash()).isEqualTo(latestCommitHash); - assertThat(commit.getBranch()).isNull(); - assertThat(commit.getMessage()).isNull(); - assertThat(commit.getAuthorEmail()).isNull(); - assertThat(commit.getAuthorName()).isNull(); + assertThat(commit.commitHash()).isEqualTo(latestCommitHash); + assertThat(commit.branch()).isNull(); + assertThat(commit.message()).isNull(); + assertThat(commit.authorEmail()).isNull(); + assertThat(commit.authorName()).isNull(); } } diff --git a/src/test/java/de/tum/in/www1/artemis/exercise/programmingexercise/ProgrammingSubmissionAndResultBitbucketBambooIntegrationTest.java b/src/test/java/de/tum/in/www1/artemis/exercise/programmingexercise/ProgrammingSubmissionAndResultBitbucketBambooIntegrationTest.java index 34815778c8e0..dc793b757d0a 100644 --- a/src/test/java/de/tum/in/www1/artemis/exercise/programmingexercise/ProgrammingSubmissionAndResultBitbucketBambooIntegrationTest.java +++ b/src/test/java/de/tum/in/www1/artemis/exercise/programmingexercise/ProgrammingSubmissionAndResultBitbucketBambooIntegrationTest.java @@ -562,8 +562,7 @@ void shouldTriggerInstructorBuildRunForLastCommit(IntegrationTestParticipationTy @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") void testCaseChanged() throws Exception { String dummyHash = "9b3a9bd71a0d80e5bbc42204c319ed3d1d4f0d6d"; - Commit commit = new Commit(); - commit.setCommitHash(dummyHash); + Commit commit = new Commit(dummyHash, null, null, null, null); setBuildAndTestAfterDueDateForProgrammingExercise(null); diff --git a/src/test/java/de/tum/in/www1/artemis/exercise/programmingexercise/ProgrammingSubmissionIntegrationTest.java b/src/test/java/de/tum/in/www1/artemis/exercise/programmingexercise/ProgrammingSubmissionIntegrationTest.java index 2206d2c1ac79..a3c7ba8577eb 100644 --- a/src/test/java/de/tum/in/www1/artemis/exercise/programmingexercise/ProgrammingSubmissionIntegrationTest.java +++ b/src/test/java/de/tum/in/www1/artemis/exercise/programmingexercise/ProgrammingSubmissionIntegrationTest.java @@ -491,7 +491,7 @@ void testNotifyPush_commitIsDifferentBranch() throws Exception { Commit mockCommit = mock(Commit.class); doReturn(mockCommit).when(versionControlService).getLastCommitDetails(any()); doReturn("branch").when(versionControlService).getDefaultBranchOfRepository(any()); - doReturn("another-branch").when(mockCommit).getBranch(); + doReturn("another-branch").when(mockCommit).branch(); String url = "/api/public/programming-submissions/" + participation.getId(); request.postWithoutLocation(url, "test", HttpStatus.OK, new HttpHeaders()); @@ -506,10 +506,10 @@ void testNotifyPush_isSetupCommit() throws Exception { doReturn(mockCommit).when(versionControlService).getLastCommitDetails(any()); doReturn("default-branch").when(versionControlService).getDefaultBranchOfRepository(any()); - doReturn("default-branch").when(mockCommit).getBranch(); - doReturn(artemisGitName).when(mockCommit).getAuthorName(); - doReturn(artemisGitEmail).when(mockCommit).getAuthorEmail(); - doReturn(SETUP_COMMIT_MESSAGE).when(mockCommit).getMessage(); + doReturn("default-branch").when(mockCommit).branch(); + doReturn(artemisGitName).when(mockCommit).authorName(); + doReturn(artemisGitEmail).when(mockCommit).authorEmail(); + doReturn(SETUP_COMMIT_MESSAGE).when(mockCommit).message(); String url = "/api/public/programming-submissions/" + participation.getId(); request.postWithoutLocation(url, "test", HttpStatus.OK, new HttpHeaders()); @@ -526,11 +526,11 @@ void testNotifyPush_studentCommitUpdatesSubmissionCount() throws Exception { doReturn(mockCommit).when(versionControlService).getLastCommitDetails(any()); doReturn("default-branch").when(versionControlService).getDefaultBranchOfRepository(any()); - doReturn("hash1").when(mockCommit).getCommitHash(); - doReturn("default-branch").when(mockCommit).getBranch(); - doReturn("Student 1").when(mockCommit).getAuthorName(); - doReturn("student@tum.de").when(mockCommit).getAuthorEmail(); - doReturn("my nice little solution").when(mockCommit).getMessage(); + doReturn("hash1").when(mockCommit).commitHash(); + doReturn("default-branch").when(mockCommit).branch(); + doReturn("Student 1").when(mockCommit).authorName(); + doReturn("student@tum.de").when(mockCommit).authorEmail(); + doReturn("my nice little solution").when(mockCommit).message(); String url = "/api/public/programming-submissions/" + participation.getId(); // no request body needed since the commit information are mocked above @@ -540,7 +540,7 @@ void testNotifyPush_studentCommitUpdatesSubmissionCount() throws Exception { argThat(arg -> arg instanceof SubmissionDTO submissionDTO && submissionDTO.participation().submissionCount() == 1)); // second push - doReturn("hash2").when(mockCommit).getCommitHash(); + doReturn("hash2").when(mockCommit).commitHash(); request.postWithoutLocation(url, "test", HttpStatus.OK, null); verify(websocketMessagingService, timeout(2000)).sendMessageToUser(eq(TEST_PREFIX + "student1"), eq(NEW_SUBMISSION_TOPIC), From bdd03ce61f1e0ff8975f52fa3f0eab14745d1f33 Mon Sep 17 00:00:00 2001 From: Mateus Messias Mendes <120647937+mateusmm01@users.noreply.github.com> Date: Thu, 4 Jan 2024 13:55:33 +0100 Subject: [PATCH 02/25] Development: Automatically update build queues in local continuous integration (#7836) --- .../LocalCISharedBuildJobQueueService.java | 54 ++++++++++- .../LocalCIBuildQueueWebsocketService.java | 93 +++++++++++++++++++ .../build-queue/build-queue.component.html | 6 -- .../build-queue/build-queue.component.ts | 62 +++++++++++-- src/main/webapp/i18n/de/buildQueue.json | 1 - src/main/webapp/i18n/en/buildQueue.json | 1 - 6 files changed, 197 insertions(+), 20 deletions(-) create mode 100644 src/main/java/de/tum/in/www1/artemis/web/websocket/localci/LocalCIBuildQueueWebsocketService.java diff --git a/src/main/java/de/tum/in/www1/artemis/service/connectors/localci/LocalCISharedBuildJobQueueService.java b/src/main/java/de/tum/in/www1/artemis/service/connectors/localci/LocalCISharedBuildJobQueueService.java index 2fe7505dfcea..ab06010c4f7f 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/connectors/localci/LocalCISharedBuildJobQueueService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/connectors/localci/LocalCISharedBuildJobQueueService.java @@ -19,6 +19,8 @@ import com.hazelcast.core.HazelcastInstance; import com.hazelcast.cp.lock.FencedLock; import com.hazelcast.map.IMap; +import com.hazelcast.map.listener.EntryAddedListener; +import com.hazelcast.map.listener.EntryRemovedListener; import de.tum.in.www1.artemis.domain.Result; import de.tum.in.www1.artemis.domain.participation.Participation; @@ -31,6 +33,7 @@ import de.tum.in.www1.artemis.service.connectors.localci.dto.LocalCIBuildResult; import de.tum.in.www1.artemis.service.programming.ProgrammingExerciseGradingService; import de.tum.in.www1.artemis.service.programming.ProgrammingMessagingService; +import de.tum.in.www1.artemis.web.websocket.localci.LocalCIBuildQueueWebsocketService; import de.tum.in.www1.artemis.web.websocket.programmingSubmission.BuildTriggerWebsocketError; @Service @@ -74,11 +77,13 @@ public class LocalCISharedBuildJobQueueService { */ private final ReentrantLock instanceLock = new ReentrantLock(); + private final LocalCIBuildQueueWebsocketService localCIBuildQueueWebsocketService; + @Autowired public LocalCISharedBuildJobQueueService(HazelcastInstance hazelcastInstance, ExecutorService localCIBuildExecutorService, LocalCIBuildJobManagementService localCIBuildJobManagementService, ParticipationRepository participationRepository, ProgrammingExerciseGradingService programmingExerciseGradingService, ProgrammingMessagingService programmingMessagingService, - ProgrammingExerciseRepository programmingExerciseRepository) { + ProgrammingExerciseRepository programmingExerciseRepository, LocalCIBuildQueueWebsocketService localCIBuildQueueWebsocketService) { this.hazelcastInstance = hazelcastInstance; this.localCIBuildExecutorService = (ThreadPoolExecutor) localCIBuildExecutorService; this.localCIBuildJobManagementService = localCIBuildJobManagementService; @@ -90,7 +95,10 @@ public LocalCISharedBuildJobQueueService(HazelcastInstance hazelcastInstance, Ex this.processingJobs = this.hazelcastInstance.getMap("processingJobs"); this.sharedLock = this.hazelcastInstance.getCPSubsystem().getLock("buildJobQueueLock"); this.queue = this.hazelcastInstance.getQueue("buildJobQueue"); - this.queue.addItemListener(new BuildJobItemListener(), true); + this.queue.addItemListener(new QueuedBuildJobItemListener(), true); + this.processingJobs.addLocalEntryListener(new ProcessingBuildJobItemListener()); + this.buildAgentInformation.addLocalEntryListener(new BuildAgentListener()); + this.localCIBuildQueueWebsocketService = localCIBuildQueueWebsocketService; } /** @@ -398,17 +406,57 @@ private ProgrammingExerciseParticipation retrieveParticipationWithRetry(Long par throw new IllegalStateException("Could not retrieve participation with id " + participationId + " from database after " + maxRetries + " retries."); } - private class BuildJobItemListener implements ItemListener { + private class QueuedBuildJobItemListener implements ItemListener { @Override public void itemAdded(ItemEvent item) { log.debug("CIBuildJobQueueItem added to queue: {}", item.getItem()); checkAvailabilityAndProcessNextBuild(); + localCIBuildQueueWebsocketService.sendQueuedBuildJobs(getQueuedJobs()); + long courseID = item.getItem().getCourseId(); + localCIBuildQueueWebsocketService.sendQueuedBuildJobsForCourse(courseID, getQueuedJobsForCourse(courseID)); } @Override public void itemRemoved(ItemEvent item) { log.debug("CIBuildJobQueueItem removed from queue: {}", item.getItem()); + localCIBuildQueueWebsocketService.sendQueuedBuildJobs(getQueuedJobs()); + long courseID = item.getItem().getCourseId(); + localCIBuildQueueWebsocketService.sendQueuedBuildJobsForCourse(courseID, getQueuedJobsForCourse(courseID)); + } + } + + private class ProcessingBuildJobItemListener implements EntryAddedListener, EntryRemovedListener { + + @Override + public void entryAdded(com.hazelcast.core.EntryEvent event) { + log.debug("CIBuildJobQueueItem added to processing jobs: {}", event.getValue()); + localCIBuildQueueWebsocketService.sendRunningBuildJobs(getProcessingJobs()); + long courseID = event.getValue().getCourseId(); + localCIBuildQueueWebsocketService.sendRunningBuildJobsForCourse(courseID, getProcessingJobsForCourse(courseID)); + } + + @Override + public void entryRemoved(com.hazelcast.core.EntryEvent event) { + log.debug("CIBuildJobQueueItem removed from processing jobs: {}", event.getOldValue()); + localCIBuildQueueWebsocketService.sendRunningBuildJobs(getProcessingJobs()); + long courseID = event.getOldValue().getCourseId(); + localCIBuildQueueWebsocketService.sendRunningBuildJobsForCourse(courseID, getProcessingJobsForCourse(courseID)); + } + } + + private class BuildAgentListener implements EntryAddedListener, EntryRemovedListener { + + @Override + public void entryAdded(com.hazelcast.core.EntryEvent event) { + log.debug("Build agent added: {}", event.getValue()); + localCIBuildQueueWebsocketService.sendBuildAgentInformation(getBuildAgentInformation()); + } + + @Override + public void entryRemoved(com.hazelcast.core.EntryEvent event) { + log.debug("Build agent removed: {}", event.getOldValue()); + localCIBuildQueueWebsocketService.sendBuildAgentInformation(getBuildAgentInformation()); } } } diff --git a/src/main/java/de/tum/in/www1/artemis/web/websocket/localci/LocalCIBuildQueueWebsocketService.java b/src/main/java/de/tum/in/www1/artemis/web/websocket/localci/LocalCIBuildQueueWebsocketService.java new file mode 100644 index 000000000000..2d72397f998a --- /dev/null +++ b/src/main/java/de/tum/in/www1/artemis/web/websocket/localci/LocalCIBuildQueueWebsocketService.java @@ -0,0 +1,93 @@ +package de.tum.in.www1.artemis.web.websocket.localci; + +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Service; + +import de.tum.in.www1.artemis.service.WebsocketMessagingService; +import de.tum.in.www1.artemis.service.connectors.localci.dto.LocalCIBuildAgentInformation; +import de.tum.in.www1.artemis.service.connectors.localci.dto.LocalCIBuildJobQueueItem; + +/** + * This service sends out websocket messages for the local continuous integration system. + * It is used to send queued and running build jobs to the client. + * It is also used to send build agent information to the client. + */ +@Service +@Profile("localci") +public class LocalCIBuildQueueWebsocketService { + + private final Logger log = LoggerFactory.getLogger(LocalCIBuildQueueWebsocketService.class); + + private final WebsocketMessagingService websocketMessagingService; + + /** + * Constructor for dependency injection + * + * @param websocketMessagingService the websocket messaging service + */ + public LocalCIBuildQueueWebsocketService(WebsocketMessagingService websocketMessagingService) { + this.websocketMessagingService = websocketMessagingService; + } + + /** + * Sends the queued build jobs for the given course over websocket. + * + * @param courseId the id of the course for which to send the queued build jobs + * @param buildJobQueue the queued build jobs + */ + + public void sendQueuedBuildJobsForCourse(long courseId, List buildJobQueue) { + String channel = "/topic/courses/" + courseId + "/queued-jobs"; + log.debug("Sending message on topic {}: {}", channel, buildJobQueue); + websocketMessagingService.sendMessage(channel, buildJobQueue); + } + + /** + * Sends the running build jobs for the given course over websocket. + * + * @param courseId the id of the course for which to send the running build jobs + * @param buildJobsRunning the running build jobs + */ + public void sendRunningBuildJobsForCourse(long courseId, List buildJobsRunning) { + String channel = "/topic/courses/" + courseId + "/running-jobs"; + log.debug("Sending message on topic {}: {}", channel, buildJobsRunning); + websocketMessagingService.sendMessage(channel, buildJobsRunning); + } + + /** + * Sends the queued build jobs over websocket. This is only allowed for admins. + * + * @param buildJobQueue the queued build jobs + */ + public void sendQueuedBuildJobs(List buildJobQueue) { + String channel = "/topic/admin/queued-jobs"; + log.debug("Sending message on topic {}: {}", channel, buildJobQueue); + websocketMessagingService.sendMessage(channel, buildJobQueue); + } + + /** + * Sends the running build jobs over websocket. This is only allowed for admins. + * + * @param buildJobQueue the running build jobs + */ + public void sendRunningBuildJobs(List buildJobQueue) { + String channel = "/topic/admin/running-jobs"; + log.debug("Sending message on topic {}: {}", channel, buildJobQueue); + websocketMessagingService.sendMessage(channel, buildJobQueue); + } + + /** + * Sends the build agent information over websocket. This is only allowed for admins. + * + * @param buildAgentInfo the build agent information + */ + public void sendBuildAgentInformation(List buildAgentInfo) { + String channel = "/topic/admin/build-agents"; + log.debug("Sending message on topic {}: {}", channel, buildAgentInfo); + websocketMessagingService.sendMessage(channel, buildAgentInfo); + } +} diff --git a/src/main/webapp/app/localci/build-queue/build-queue.component.html b/src/main/webapp/app/localci/build-queue/build-queue.component.html index 575c938a5a00..fd2be4b4d383 100644 --- a/src/main/webapp/app/localci/build-queue/build-queue.component.html +++ b/src/main/webapp/app/localci/build-queue/build-queue.component.html @@ -1,10 +1,4 @@
-

- Build job queue information - -

Running build jobs

diff --git a/src/main/webapp/app/localci/build-queue/build-queue.component.ts b/src/main/webapp/app/localci/build-queue/build-queue.component.ts index 574443779c76..1881ba2b29c5 100644 --- a/src/main/webapp/app/localci/build-queue/build-queue.component.ts +++ b/src/main/webapp/app/localci/build-queue/build-queue.component.ts @@ -1,32 +1,76 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnDestroy, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; -import { AccountService } from 'app/core/auth/account.service'; -import { BuildQueueService } from 'app/localci/build-queue/build-queue.service'; import { BuildJob } from 'app/entities/build-job.model'; -import { faRefresh } from '@fortawesome/free-solid-svg-icons'; +import { JhiWebsocketService } from 'app/core/websocket/websocket.service'; +import { BuildQueueService } from 'app/localci/build-queue/build-queue.service'; @Component({ selector: 'jhi-build-queue', templateUrl: './build-queue.component.html', styleUrl: './build-queue.component.scss', }) -export class BuildQueueComponent implements OnInit { +export class BuildQueueComponent implements OnInit, OnDestroy { queuedBuildJobs: BuildJob[]; runningBuildJobs: BuildJob[]; - - //icons - faRefresh = faRefresh; + courseChannels: string[] = []; constructor( private route: ActivatedRoute, - private accountService: AccountService, + private websocketService: JhiWebsocketService, private buildQueueService: BuildQueueService, ) {} ngOnInit() { this.load(); + this.initWebsocketSubscription(); + } + + /** + * This method is used to unsubscribe from the websocket channels when the component is destroyed. + */ + ngOnDestroy() { + this.websocketService.unsubscribe(`/topic/admin/queued-jobs`); + this.websocketService.unsubscribe(`/topic/admin/running-jobs`); + this.courseChannels.forEach((channel) => { + this.websocketService.unsubscribe(channel); + }); + } + + /** + * This method is used to initialize the websocket subscription for the build jobs. It subscribes to the channels for the queued and running build jobs. + */ + initWebsocketSubscription() { + this.route.paramMap.subscribe((params) => { + const courseId = Number(params.get('courseId')); + if (courseId) { + this.websocketService.subscribe(`/topic/courses/${courseId}/queued-jobs`); + this.websocketService.subscribe(`/topic/courses/${courseId}/running-jobs`); + this.websocketService.receive(`/topic/courses/${courseId}/queued-jobs`).subscribe((queuedBuildJobs) => { + this.queuedBuildJobs = queuedBuildJobs; + }); + this.websocketService.receive(`/topic/courses/${courseId}/running-jobs`).subscribe((runningBuildJobs) => { + this.runningBuildJobs = runningBuildJobs; + }); + this.courseChannels.push(`/topic/courses/${courseId}/queued-jobs`); + this.courseChannels.push(`/topic/courses/${courseId}/running-jobs`); + } else { + this.websocketService.subscribe(`/topic/admin/queued-jobs`); + this.websocketService.subscribe(`/topic/admin/running-jobs`); + this.websocketService.receive(`/topic/admin/queued-jobs`).subscribe((queuedBuildJobs) => { + this.queuedBuildJobs = queuedBuildJobs; + }); + this.websocketService.receive(`/topic/admin/running-jobs`).subscribe((runningBuildJobs) => { + this.runningBuildJobs = runningBuildJobs; + }); + } + }); } + /** + * This method is used to load the build jobs from the backend when the component is initialized. + * This ensures that the table is filled with data when the page is loaded or refreshed otherwise the user needs to + * wait until the websocket subscription receives the data. + */ load() { this.route.paramMap.subscribe((params) => { const courseId = Number(params.get('courseId')); diff --git a/src/main/webapp/i18n/de/buildQueue.json b/src/main/webapp/i18n/de/buildQueue.json index 49b49f9607d3..2c1d19e2ca54 100644 --- a/src/main/webapp/i18n/de/buildQueue.json +++ b/src/main/webapp/i18n/de/buildQueue.json @@ -3,7 +3,6 @@ "buildQueue": { "title": "Build Warteschlange", "pageTitle": "Build Job Informationen", - "refreshLabel": "Aktualisieren", "queuedBuildJobs": "Wartende Build Jobs", "runningBuildJobs": "Laufende Build Jobs", "buildJob": { diff --git a/src/main/webapp/i18n/en/buildQueue.json b/src/main/webapp/i18n/en/buildQueue.json index ad0c55b51c94..57ef6f8d4ced 100644 --- a/src/main/webapp/i18n/en/buildQueue.json +++ b/src/main/webapp/i18n/en/buildQueue.json @@ -3,7 +3,6 @@ "buildQueue": { "title": "Build Queue", "pageTitle": "Build Job Information", - "refreshLabel": "Refresh", "queuedBuildJobs": "Queued Build Jobs", "runningBuildJobs": "Running Build Jobs", "buildJob": { From d76fc4171b93faddf61b7383fb910d5ca57f7948 Mon Sep 17 00:00:00 2001 From: Laurenz Blumentritt <38919977+laurenzfb@users.noreply.github.com> Date: Thu, 4 Jan 2024 13:56:58 +0100 Subject: [PATCH 03/25] Development: Remove stranded build containers on server startup in local continuous integration (#7835) --- .../localvcci/LocalCIConfiguration.java | 27 +++++++++---------- .../LocalCIBuildJobManagementService.java | 5 +++- .../resources/config/application-localci.yml | 2 ++ 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/main/java/de/tum/in/www1/artemis/config/localvcci/LocalCIConfiguration.java b/src/main/java/de/tum/in/www1/artemis/config/localvcci/LocalCIConfiguration.java index 5cac90734755..62cb30c71fe3 100644 --- a/src/main/java/de/tum/in/www1/artemis/config/localvcci/LocalCIConfiguration.java +++ b/src/main/java/de/tum/in/www1/artemis/config/localvcci/LocalCIConfiguration.java @@ -48,21 +48,13 @@ public class LocalCIConfiguration { @Value("${artemis.continuous-integration.specify-thread-pool-size:false}") boolean specifyThreadPoolSize; + @Value("${artemis.continuous-integration.build-container-prefix:local-ci-}") + private String buildContainerPrefix; + public LocalCIConfiguration(ProgrammingLanguageConfiguration programmingLanguageConfiguration) { this.programmingLanguageConfiguration = programmingLanguageConfiguration; } - /** - * Defines the thread pool size for the local CI ExecutorService based on system resources. - * - * @return The thread pool size bean. - */ - @Bean - public int calculatedThreadPoolSize() { - int availableProcessors = Runtime.getRuntime().availableProcessors(); - return Math.max(1, (availableProcessors - 2) / 2); - } - /** * Creates a HostConfig object that is used to configure the Docker container for build jobs. * The configuration is based on the default Docker flags for build jobs as specified in artemis.continuous-integration.build. @@ -101,11 +93,10 @@ public HostConfig hostConfig() { /** * Creates an executor service that manages the queue of build jobs. * - * @param calculatedThreadPoolSize The calculatedThreadPoolSize bean. * @return The executor service bean. */ @Bean - public ExecutorService localCIBuildExecutorService(int calculatedThreadPoolSize) { + public ExecutorService localCIBuildExecutorService() { int threadPoolSize; @@ -113,7 +104,8 @@ public ExecutorService localCIBuildExecutorService(int calculatedThreadPoolSize) threadPoolSize = fixedThreadPoolSize; } else { - threadPoolSize = calculatedThreadPoolSize; + int availableProcessors = Runtime.getRuntime().availableProcessors(); + threadPoolSize = Math.max(1, (availableProcessors - 2) / 2); } log.info("Using ExecutorService with thread pool size {} and a queue size limit of {}.", threadPoolSize, queueSizeLimit); @@ -153,6 +145,13 @@ public DockerClient dockerClient() { log.info("Docker client created with connection URI: {}", dockerConnectionUri); + // remove all stranded build containers + dockerClient.listContainersCmd().withShowAll(true).exec().forEach(container -> { + if (container.getNames()[0].startsWith("/" + buildContainerPrefix)) { + dockerClient.removeContainerCmd(container.getId()).withForce(true).exec(); + } + }); + return dockerClient; } diff --git a/src/main/java/de/tum/in/www1/artemis/service/connectors/localci/LocalCIBuildJobManagementService.java b/src/main/java/de/tum/in/www1/artemis/service/connectors/localci/LocalCIBuildJobManagementService.java index b6ea7a1c34a5..0424633278a9 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/connectors/localci/LocalCIBuildJobManagementService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/connectors/localci/LocalCIBuildJobManagementService.java @@ -54,6 +54,9 @@ public class LocalCIBuildJobManagementService { @Value("${artemis.continuous-integration.asynchronous:true}") private boolean runBuildJobsAsynchronously; + @Value("${artemis.continuous-integration.build-container-prefix:local-ci-}") + private String buildContainerPrefix; + public LocalCIBuildJobManagementService(LocalCIBuildJobExecutionService localCIBuildJobExecutionService, ExecutorService localCIBuildExecutorService, ProgrammingMessagingService programmingMessagingService, LocalCIBuildPlanService localCIBuildPlanService, LocalCIContainerService localCIContainerService, LocalCIDockerService localCIDockerService, ProgrammingLanguageConfiguration programmingLanguageConfiguration) { @@ -88,7 +91,7 @@ public CompletableFuture executeBuildJob(ProgrammingExercise localCIDockerService.pullDockerImage(dockerImage); // Prepare the Docker container name before submitting the build job to the executor service, so we can remove the container if something goes wrong. - String containerName = "local-ci-" + participation.getId() + "-" + ZonedDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS")); + String containerName = buildContainerPrefix + participation.getId() + "-" + ZonedDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS")); // Prepare a Callable that will later be called. It contains the actual steps needed to execute the build job. Callable buildJob = () -> localCIBuildJobExecutionService.runBuildJob(participation, commitHash, isPushToTestRepository, containerName, dockerImage); diff --git a/src/main/resources/config/application-localci.yml b/src/main/resources/config/application-localci.yml index 90ca419e63f6..5c631e0d58e1 100644 --- a/src/main/resources/config/application-localci.yml +++ b/src/main/resources/config/application-localci.yml @@ -19,6 +19,8 @@ artemis: queue-size-limit: 100 # The path to the local CI build scripts. This path is relative to the Artemis root directory. local-cis-build-scripts-path: local-ci-scripts + # The prefix that is used for the Docker containers that are created by the local CI system. + build-container-prefix: local-ci- # In case you need to use a proxy to access the internet from the Docker container (e.g., due to firewall constraints), set use-system-proxy to true and configure the proxy settings below. proxies: use-system-proxy: false From 002538346660b4500073790b06009a156a04c433 Mon Sep 17 00:00:00 2001 From: Stephan Krusche Date: Thu, 4 Jan 2024 21:10:05 +0100 Subject: [PATCH 04/25] Development: Refactor VcsRepositoryUrl to VcsRepositoryUri (#7838) --- docs/dev/system-design/DataModel.svg | 2 +- .../localvc-localci-system-design.rst | 2 +- docs/user/exams/students_guide.rst | 4 +- .../artemis/config/SecurityConfiguration.java | 7 +- .../entries/BambooMigrationService.java | 14 +- .../entries/CIVCSMigrationService.java | 22 +- .../GitLabJenkinsMigrationService.java | 26 +-- .../MigrationEntry20230808_203400.java | 64 +++--- .../MigrationEntry20230920_181600.java | 18 +- .../tum/in/www1/artemis/domain/Authority.java | 10 +- .../artemis/domain/AuxiliaryRepository.java | 44 ++-- .../artemis/domain/NotificationSetting.java | 47 ----- .../artemis/domain/ProgrammingExercise.java | 84 ++++---- .../in/www1/artemis/domain/Repository.java | 21 +- ...positoryUrl.java => VcsRepositoryUri.java} | 17 +- .../ProgrammingExerciseSolutionEntry.java | 18 -- .../hestia/ProgrammingExerciseTask.java | 16 -- .../iris/message/IrisTextMessageContent.java | 7 - ...tBaseProgrammingExerciseParticipation.java | 12 +- .../ProgrammingExerciseParticipation.java | 28 +-- ...ogrammingExerciseStudentParticipation.java | 12 +- .../plagiarism/PlagiarismComparison.java | 22 -- .../plagiarism/PlagiarismSubmission.java | 21 -- ...xerciseStudentParticipationRepository.java | 4 +- .../service/ConsistencyCheckService.java | 8 +- .../www1/artemis/service/ExerciseService.java | 2 +- .../artemis/service/InternalUrlService.java | 38 ++-- .../artemis/service/ParticipationService.java | 52 ++--- .../artemis/service/RepositoryService.java | 12 +- .../in/www1/artemis/service/TeamService.java | 4 +- .../{UrlService.java => UriService.java} | 108 +++++----- .../service/connectors/GitService.java | 198 +++++++++--------- .../aeolus/AeolusBuildPlanService.java | 20 +- .../bamboo/BambooBuildPlanService.java | 104 +++++---- .../connectors/bamboo/BambooService.java | 36 ++-- .../BambooBuildPlanUpdateService.java | 8 +- .../bitbucket/BitbucketService.java | 100 ++++----- .../ci/ContinuousIntegrationService.java | 16 +- .../ContinuousIntegrationUpdateService.java | 4 +- .../connectors/gitlab/GitLabService.java | 98 ++++----- .../connectors/gitlabci/GitLabCIService.java | 38 ++-- .../gitlabci/GitLabCITriggerService.java | 16 +- .../connectors/jenkins/JenkinsService.java | 12 +- .../jenkins/JenkinsXmlConfigBuilder.java | 14 +- .../build_plan/JenkinsBuildPlanCreator.java | 6 +- .../build_plan/JenkinsBuildPlanService.java | 36 ++-- .../build_plan/JenkinsBuildPlanUtils.java | 2 +- .../LocalCIBuildJobExecutionService.java | 34 +-- .../LocalCIBuildJobManagementService.java | 2 +- .../connectors/localci/LocalCIService.java | 8 +- .../localci/LocalCITriggerService.java | 2 +- ...toryUrl.java => LocalVCRepositoryUri.java} | 26 +-- .../connectors/localvc/LocalVCService.java | 62 +++--- .../localvc/LocalVCServletService.java | 42 ++-- .../vcs/AbstractVersionControlService.java | 61 +++--- .../connectors/vcs/VersionControlService.java | 38 ++-- .../dto/athena/ProgrammingExerciseDTO.java | 2 +- .../dto/athena/ProgrammingSubmissionDTO.java | 2 +- .../artemis/service/exam/ExamService.java | 4 +- .../export/DataExportExamCreationService.java | 4 +- .../DataExportExerciseCreationService.java | 4 +- .../ProgrammingExerciseExportService.java | 44 ++-- ...ogrammingExerciseGitDiffReportService.java | 14 +- .../hestia/TestwiseCoverageService.java | 14 +- .../behavioral/BehavioralBlackboard.java | 2 +- .../behavioral/BehavioralTestCaseService.java | 2 +- .../hestia/behavioral/GroupedFile.java | 53 +---- ...ddUncoveredLinesAsPotentialCodeBlocks.java | 4 +- .../knowledgesource/CombineChangeBlocks.java | 4 +- .../CreateSolutionEntries.java | 4 +- .../structural/StructuralTestCaseService.java | 4 +- .../iris/session/IrisChatSessionService.java | 6 +- .../session/IrisCodeEditorSessionService.java | 6 +- .../FirebasePushNotificationService.java | 2 +- .../PushNotificationService.java | 2 +- ...ProgrammingPlagiarismDetectionService.java | 20 +- ...ProgrammingExerciseImportBasicService.java | 4 +- ...grammingExerciseImportFromFileService.java | 6 +- .../ProgrammingExerciseImportService.java | 46 ++-- ...ogrammingExerciseParticipationService.java | 24 +-- .../ProgrammingExerciseRepositoryService.java | 52 ++--- .../ProgrammingExerciseService.java | 34 +-- .../ProgrammingSubmissionService.java | 6 +- ...aticProgrammingExerciseCleanupService.java | 8 +- .../ProgrammingExerciseScheduleService.java | 2 +- .../www1/artemis/service/util/UrlUtils.java | 2 +- .../rest/NotificationSettingsResource.java | 2 +- .../web/rest/ParticipationResource.java | 2 +- ...ogrammingExerciseExportImportResource.java | 2 +- ...grammingExerciseParticipationResource.java | 10 +- .../web/rest/ProgrammingExerciseResource.java | 22 +- ...grammingExerciseParticipationResource.java | 14 +- .../rest/repository/RepositoryResource.java | 12 +- .../repository/TestRepositoryResource.java | 10 +- .../assessment-instructions.component.html | 2 +- ...ng-exercise-student-participation.model.ts | 4 +- ...rogramming-exercise-participation.model.ts | 2 +- ...rogramming-exercise-participation.model.ts | 2 +- ...ing-exercise-auxiliary-repository-model.ts | 2 +- .../entities/programming-exercise.model.ts | 2 +- .../exercise-groups.component.html | 2 +- ...ramming-exercise-group-cell.component.html | 14 +- ...ogramming-exercise-group-cell.component.ts | 2 +- .../programming-exam-summary.component.html | 2 +- ...-tutor-assessment-container.component.html | 2 +- ...ructor-and-editor-container.component.html | 6 +- ...tor-instructor-base-container.component.ts | 8 +- ...programming-exercise-detail.component.html | 40 ++-- .../programming-exercise.component.html | 12 +- .../utils/programming-exercise.utils.ts | 2 +- ...ercise-assessment-dashboard.component.html | 4 +- ...exercise-scores-export-button.component.ts | 2 +- .../exercise-scores.component.html | 2 +- .../exercise-scores.component.ts | 2 +- .../participation.component.html | 10 +- .../participation/participation.component.ts | 14 +- .../participation/participation.service.ts | 2 +- ...-and-editor-orion-container.component.html | 4 +- ...rcise-details-student-actions.component.ts | 2 +- .../clone-repo-button.component.html | 8 +- .../clone-repo-button.component.ts | 34 +-- src/main/webapp/i18n/de/participation.json | 2 +- .../webapp/i18n/de/programmingExercise.json | 6 +- src/main/webapp/i18n/en/error.json | 4 +- src/main/webapp/i18n/en/participation.json | 2 +- .../webapp/i18n/en/programmingExercise.json | 6 +- ...ingIntegrationBambooBitbucketJiraTest.java | 24 +-- ...ringIntegrationGitlabCIGitlabSamlTest.java | 8 +- ...tractSpringIntegrationIndependentTest.java | 8 +- ...actSpringIntegrationJenkinsGitlabTest.java | 8 +- ...ctSpringIntegrationLocalCILocalVCTest.java | 6 +- .../connector/BambooRequestMockProvider.java | 8 +- .../BitbucketRequestMockProvider.java | 14 +- .../connector/GitlabRequestMockProvider.java | 32 +-- .../connector/JenkinsRequestMockProvider.java | 2 +- .../artemis/connectors/AeolusServiceTest.java | 36 ++-- .../ExamParticipationIntegrationTest.java | 8 +- .../in/www1/artemis/exam/ExamStartTest.java | 2 +- .../exercise/ExerciseIntegrationTest.java | 12 +- .../ContinuousIntegrationTestService.java | 8 +- .../programmingexercise/GitServiceTest.java | 24 +-- .../GitlabServiceTest.java | 6 +- .../programmingexercise/MockDelegate.java | 10 +- .../ProgrammingExerciseFactory.java | 8 +- ...ProgrammingExerciseGitIntegrationTest.java | 4 +- ...rammingExerciseIntegrationTestService.java | 88 ++++---- ...ExerciseLocalVCLocalCIIntegrationTest.java | 30 +-- ...gExerciseParticipationIntegrationTest.java | 4 +- ...rogrammingExerciseScheduleServiceTest.java | 18 +- .../ProgrammingExerciseTest.java | 8 +- .../ProgrammingExerciseTestService.java | 104 ++++----- .../ProgrammingExerciseUtilService.java | 4 +- ...dResultBitbucketBambooIntegrationTest.java | 8 +- ...AndResultGitlabJenkinsIntegrationTest.java | 4 +- .../ProgrammingSubmissionIntegrationTest.java | 10 +- .../RepositoryIntegrationTest.java | 30 +-- .../SubmissionPolicyIntegrationTest.java | 4 +- ...TestRepositoryResourceIntegrationTest.java | 12 +- ...AbstractLocalCILocalVCIntegrationTest.java | 8 +- .../localvcci/LocalCIIntegrationTest.java | 6 +- .../localvcci/LocalVCIntegrationTest.java | 18 +- .../LocalVCLocalCIIntegrationTest.java | 2 +- ...VCLocalCIParticipationIntegrationTest.java | 8 +- .../localvcci/LocalVCLocalCITestService.java | 28 +-- .../artemis/metis/ChannelIntegrationTest.java | 2 +- .../participation/ParticipationFactory.java | 10 +- .../ParticipationIntegrationTest.java | 14 +- .../ParticipationUtilService.java | 28 +-- .../artemis/service/BitbucketServiceTest.java | 4 +- .../service/ConsistencyCheckTestService.java | 18 +- .../DataExportCreationServiceTest.java | 2 +- .../artemis/service/GitlabCIServiceTest.java | 10 +- ...ava => JenkinsInternalUriServiceTest.java} | 24 +-- .../artemis/service/JenkinsServiceTest.java | 26 +-- .../service/ParticipationServiceTest.java | 7 +- .../www1/artemis/service/UriServiceTest.java | 137 ++++++++++++ .../www1/artemis/service/UrlServiceTest.java | 137 ------------ .../AthenaFeedbackSuggestionsServiceTest.java | 2 +- .../AthenaRepositoryExportServiceTest.java | 2 +- .../AthenaSubmissionSendingServiceTest.java | 2 +- .../util/AbstractArtemisIntegrationTest.java | 22 +- .../in/www1/artemis/util/GitUtilService.java | 12 +- .../artemis/util/HestiaUtilTestService.java | 62 +++--- .../artemis/util/IrisUtilTestService.java | 62 +++--- .../util/SensitiveInformationUtil.java | 6 +- ...ming-exercise-group-cell.component.spec.ts | 8 +- ...programming-exam-summary.component.spec.ts | 4 +- ...ise-scores-export-button.component.spec.ts | 2 +- .../exercise-scores.component.spec.ts | 2 +- ...-details-student-actions.component.spec.ts | 2 +- ...tor-assessment-container.component.spec.ts | 4 +- ...gramming-exercise-update.component.spec.ts | 4 +- .../clone-repo-button.component.spec.ts | 52 ++--- .../shared/commits-info.component.spec.ts | 2 +- .../feedback/feedback-modal.component.spec.ts | 2 +- .../utils/programming-exercise.utils.spec.ts | 16 +- ...code-editor-instructor.integration.spec.ts | 38 ++-- .../service/participation.service.spec.ts | 12 +- .../programming-exercise.service.spec.ts | 8 +- .../build-plan-repository-list-response.html | 2 +- 200 files changed, 1782 insertions(+), 1979 deletions(-) rename src/main/java/de/tum/in/www1/artemis/domain/{VcsRepositoryUrl.java => VcsRepositoryUri.java} (90%) rename src/main/java/de/tum/in/www1/artemis/service/{UrlService.java => UriService.java} (52%) rename src/main/java/de/tum/in/www1/artemis/service/connectors/localvc/{LocalVCRepositoryUrl.java => LocalVCRepositoryUri.java} (90%) rename src/test/java/de/tum/in/www1/artemis/service/{JenkinsInternalUrlServiceTest.java => JenkinsInternalUriServiceTest.java} (86%) create mode 100644 src/test/java/de/tum/in/www1/artemis/service/UriServiceTest.java delete mode 100644 src/test/java/de/tum/in/www1/artemis/service/UrlServiceTest.java diff --git a/docs/dev/system-design/DataModel.svg b/docs/dev/system-design/DataModel.svg index 4c026b72401a..587cd71bccd6 100644 --- a/docs/dev/system-design/DataModel.svg +++ b/docs/dev/system-design/DataModel.svg @@ -173,7 +173,7 @@ Attributes - repositoryUrl + repositoryUri buildPlanId initializationDate presentationScore diff --git a/docs/dev/system-design/localvc-localci/localvc-localci-system-design.rst b/docs/dev/system-design/localvc-localci/localvc-localci-system-design.rst index e4692d210e43..733fc42c6987 100644 --- a/docs/dev/system-design/localvc-localci/localvc-localci-system-design.rst +++ b/docs/dev/system-design/localvc-localci/localvc-localci-system-design.rst @@ -38,7 +38,7 @@ This includes extracting the command and parameters from the client request and It reads objects and refs from the repository, updates the repository for push requests, and formats the results of the Git commands it executes into a response that it sends back to the client. This could involve sending objects and refs to the client in a packfile, or transmitting error messages. The ``Git Server`` delegates all logic connected to Artemis to the ``Local VC Servlet Service``. -This service resolves the repository from the file system depending on the repository URL. It also handles user authentication (only Basic Auth for now) and authorization. +This service resolves the repository from the file system depending on the repository URI. It also handles user authentication (only Basic Auth for now) and authorization. For authorization (e.g. "is the requesting user the owner of the repository?", "has the due date already passed?"), it uses the logic outsourced to the ``RepositoryAccessService`` that the existing online editor also uses. For push requests, the ``Local VC Servlet Service`` calls the ``processNewProgrammingSubmission()`` method of the ``Programming Submission Service`` to create a new submission and finally calls the local CI subsystem to trigger a new build. diff --git a/docs/user/exams/students_guide.rst b/docs/user/exams/students_guide.rst index 1ffbc442b9bf..45afd60199fe 100644 --- a/docs/user/exams/students_guide.rst +++ b/docs/user/exams/students_guide.rst @@ -301,7 +301,7 @@ If your instructor updates the problem statement for an exercise during the exam - **None** You can submit as many times as you want without any consequences. - **Lock Repository** There's a limit on the number of allowed submissions. Once you exceed the limit, your repository will be locked and further submissions will not be allowed. - + .. figure:: student/submission_policy_lock.png :alt: Effect of the Lock Repository Policy :align: center @@ -341,7 +341,7 @@ Summary - After you hand in, you can view the summary of your exam. - You always have access to the summary. You can find it by following the steps displayed in: `Accessing the Exam`_. - Further you have the opportunity to export the summary as a PDF file by clicking on |export_pdf|. -- The summary contains an aggregated view of all your submissions. For programming exercises, it also contains the latest commit hash and repository URL so you can review your code. +- The summary contains an aggregated view of all your submissions. For programming exercises, it also contains the latest commit hash and repository URI so you can review your code. .. figure:: student/summary.png :alt: Summary diff --git a/src/main/java/de/tum/in/www1/artemis/config/SecurityConfiguration.java b/src/main/java/de/tum/in/www1/artemis/config/SecurityConfiguration.java index a1d323f04cdf..000216f6a045 100644 --- a/src/main/java/de/tum/in/www1/artemis/config/SecurityConfiguration.java +++ b/src/main/java/de/tum/in/www1/artemis/config/SecurityConfiguration.java @@ -102,12 +102,7 @@ public PasswordEncoder passwordEncoder() { @Bean RoleHierarchy roleHierarchy() { var roleHierarchy = new RoleHierarchyImpl(); - roleHierarchy.setHierarchy(""" - ROLE_ADMIN > ROLE_INSTRUCTOR - ROLE_INSTRUCTOR > ROLE_EDITOR - ROLE_EDITOR > ROLE_TA - ROLE_TA > ROLE_USER - """); + roleHierarchy.setHierarchy("ROLE_ADMIN > ROLE_INSTRUCTOR > ROLE_EDITOR > ROLE_TA > ROLE_USER > ROLE_ANONYMOUS"); return roleHierarchy; } diff --git a/src/main/java/de/tum/in/www1/artemis/config/migration/entries/BambooMigrationService.java b/src/main/java/de/tum/in/www1/artemis/config/migration/entries/BambooMigrationService.java index cfc97d6680f1..ac42f050136c 100644 --- a/src/main/java/de/tum/in/www1/artemis/config/migration/entries/BambooMigrationService.java +++ b/src/main/java/de/tum/in/www1/artemis/config/migration/entries/BambooMigrationService.java @@ -27,7 +27,7 @@ import org.springframework.web.util.UriComponentsBuilder; import de.tum.in.www1.artemis.domain.AuxiliaryRepository; -import de.tum.in.www1.artemis.domain.VcsRepositoryUrl; +import de.tum.in.www1.artemis.domain.VcsRepositoryUri; import de.tum.in.www1.artemis.domain.enumeration.ProgrammingLanguage; import de.tum.in.www1.artemis.domain.participation.ProgrammingExerciseStudentParticipation; import de.tum.in.www1.artemis.exception.ContinuousIntegrationException; @@ -116,7 +116,7 @@ private static Optional getRepositoryNameById(String html, Long id) { } @Override - public void overrideBuildPlanNotification(String projectKey, String buildPlanKey, VcsRepositoryUrl vcsRepositoryUrl) { + public void overrideBuildPlanNotification(String projectKey, String buildPlanKey, VcsRepositoryUri repositoryUri) { Map notificationIds = getAllArtemisBuildPlanServerNotificationIds(buildPlanKey); log.info("Found {} notifications for build plan {}", notificationIds.size(), buildPlanKey); @@ -143,12 +143,12 @@ public void overrideBuildPlanNotification(String projectKey, String buildPlanKey } @Override - public void removeWebHook(VcsRepositoryUrl repositoryUrl) { + public void removeWebHook(VcsRepositoryUri repositoryUri) { // nothing to do } @Override - public void deleteBuildTriggers(String projectKey, String buildPlanKey, VcsRepositoryUrl repositoryUrl) { + public void deleteBuildTriggers(String projectKey, String buildPlanKey, VcsRepositoryUri repositoryUri) { if (buildPlanKey == null) { return; } @@ -179,7 +179,7 @@ public void checkPrerequisites() throws ContinuousIntegrationException { } @Override - public void overrideBuildPlanRepository(String buildPlanId, String name, String repositoryUrl, String defaultBranch) { + public void overrideBuildPlanRepository(String buildPlanId, String name, String repositoryUri, String defaultBranch) { if (this.sharedCredentialId.isEmpty()) { Optional credentialsId = getSharedCredential(); if (credentialsId.isEmpty()) { @@ -203,7 +203,7 @@ public void overrideBuildPlanRepository(String buildPlanId, String name, String deleteLinkedRepository(buildPlanId, repositoryId.get()); } log.debug("Adding repository {} for build plan {}", name, buildPlanId); - addGitRepository(buildPlanId, bambooInternalUrlService.toInternalVcsUrl(repositoryUrl), name, this.sharedCredentialId.orElseThrow(), defaultBranch); + addGitRepository(buildPlanId, bambooInternalUrlService.toInternalVcsUrl(repositoryUri), name, this.sharedCredentialId.orElseThrow(), defaultBranch); } /** @@ -549,7 +549,7 @@ private void addGitRepository(String buildPlanKey, String repository, String nam body.add("selectedRepository", "com.atlassian.bamboo.plugins.atlassian-bamboo-plugin-git:gitv2"); body.add("respositoryPluginKey", "com.atlassian.bamboo.plugins.atlassian-bamboo-plugin-git:gitv2"); body.add("repositoryName", name); - body.add("repository.git.repositoryUrl", repository); + body.add("repository.git.repositoryUri", repository); body.add("repository.git.authenticationType", "PASSWORD"); body.add("selectFields", "repository.git.authenticationType"); body.add("repository.git.passwordCredentialsSource", "SHARED_CREDENTIALS"); diff --git a/src/main/java/de/tum/in/www1/artemis/config/migration/entries/CIVCSMigrationService.java b/src/main/java/de/tum/in/www1/artemis/config/migration/entries/CIVCSMigrationService.java index 46992d1bd9c4..b86631b8869c 100644 --- a/src/main/java/de/tum/in/www1/artemis/config/migration/entries/CIVCSMigrationService.java +++ b/src/main/java/de/tum/in/www1/artemis/config/migration/entries/CIVCSMigrationService.java @@ -6,7 +6,7 @@ import org.springframework.data.domain.Pageable; import de.tum.in.www1.artemis.domain.AuxiliaryRepository; -import de.tum.in.www1.artemis.domain.VcsRepositoryUrl; +import de.tum.in.www1.artemis.domain.VcsRepositoryUri; import de.tum.in.www1.artemis.domain.enumeration.ProgrammingLanguage; import de.tum.in.www1.artemis.domain.participation.ProgrammingExerciseStudentParticipation; import de.tum.in.www1.artemis.exception.ContinuousIntegrationException; @@ -23,31 +23,31 @@ public interface CIVCSMigrationService { * * @param projectKey The key of the project, e.g. 'EIST16W1', which is normally the programming exercise project key. * @param buildPlanKey The key of the build plan, which is usually the name combined with the project, e.g. 'EIST16W1-GA56HUR'. - * @param repositoryUrl the URL of the assignment repository + * @param repositoryUri the URI of the assignment repository */ - void overrideBuildPlanNotification(String projectKey, String buildPlanKey, VcsRepositoryUrl repositoryUrl); + void overrideBuildPlanNotification(String projectKey, String buildPlanKey, VcsRepositoryUri repositoryUri); /** * Deletes all build triggers for the given build plan. * * @param projectKey The key of the project, e.g. 'EIST16W1', which is normally the programming exercise project key. * @param buildPlanKey The key of the build plan, which is usually the name combined with the project, e.g. 'EIST16W1-GA56HUR'. - * @param repositoryUrl the URL of the repository that triggers the build + * @param repositoryUri the URI of the repository that triggers the build */ - void deleteBuildTriggers(String projectKey, String buildPlanKey, VcsRepositoryUrl repositoryUrl); + void deleteBuildTriggers(String projectKey, String buildPlanKey, VcsRepositoryUri repositoryUri); /** - * Overrides the existing repository URL for the given build plan. + * Overrides the existing repository URI for the given build plan. * * @param buildPlanKey The key of the build plan, which is usually the name combined with the project, e.g. 'EIST16W1-BASE'. * @param name The name of the repository - * @param repositoryUrl the URL of the repository + * @param repositoryUri the URI of the repository * @param defaultBranch the default branch of the exercise to be migrated */ - void overrideBuildPlanRepository(String buildPlanKey, String name, String repositoryUrl, String defaultBranch); + void overrideBuildPlanRepository(String buildPlanKey, String name, String repositoryUri, String defaultBranch); /** - * Overrides the existing repository URL for the given project that are checked out by the build plans. + * Overrides the existing repository URI for the given project that are checked out by the build plans. * * @param buildPlanKey The key of the build plan, which is usually the name combined with the project, e.g. 'EIST16W1-BASE'. * @param auxiliaryRepositoryList the list of auxiliary repositories @@ -76,9 +76,9 @@ Page getPageableStudentParticipations( /** * Remove the web hooks for the given repository. * - * @param repositoryUrl + * @param repositoryUri the repository uri for which the webhook should be removed */ - void removeWebHook(VcsRepositoryUrl repositoryUrl); + void removeWebHook(VcsRepositoryUri repositoryUri); /** * Checks if the build plan exists. diff --git a/src/main/java/de/tum/in/www1/artemis/config/migration/entries/GitLabJenkinsMigrationService.java b/src/main/java/de/tum/in/www1/artemis/config/migration/entries/GitLabJenkinsMigrationService.java index 4f221ef9a1dd..326579185bdb 100644 --- a/src/main/java/de/tum/in/www1/artemis/config/migration/entries/GitLabJenkinsMigrationService.java +++ b/src/main/java/de/tum/in/www1/artemis/config/migration/entries/GitLabJenkinsMigrationService.java @@ -21,13 +21,13 @@ import org.w3c.dom.Document; import de.tum.in.www1.artemis.domain.AuxiliaryRepository; -import de.tum.in.www1.artemis.domain.VcsRepositoryUrl; +import de.tum.in.www1.artemis.domain.VcsRepositoryUri; import de.tum.in.www1.artemis.domain.enumeration.ProgrammingLanguage; import de.tum.in.www1.artemis.domain.participation.ProgrammingExerciseStudentParticipation; import de.tum.in.www1.artemis.exception.ContinuousIntegrationException; import de.tum.in.www1.artemis.exception.JenkinsException; import de.tum.in.www1.artemis.repository.ProgrammingExerciseStudentParticipationRepository; -import de.tum.in.www1.artemis.service.UrlService; +import de.tum.in.www1.artemis.service.UriService; import de.tum.in.www1.artemis.service.connectors.gitlab.GitLabException; import de.tum.in.www1.artemis.service.connectors.jenkins.JenkinsService; import de.tum.in.www1.artemis.service.connectors.jenkins.jobs.JenkinsJobService; @@ -55,19 +55,19 @@ public class GitLabJenkinsMigrationService implements CIVCSMigrationService { private final JenkinsService jenkinsService; - private final UrlService urlService; + private final UriService uriService; private final GitLabApi gitlab; - public GitLabJenkinsMigrationService(JenkinsJobService jenkinsJobService, GitLabApi gitlab, JenkinsService jenkinsService, UrlService urlService) { + public GitLabJenkinsMigrationService(JenkinsJobService jenkinsJobService, GitLabApi gitlab, JenkinsService jenkinsService, UriService uriService) { this.jenkinsJobService = jenkinsJobService; this.gitlab = gitlab; this.jenkinsService = jenkinsService; - this.urlService = urlService; + this.uriService = uriService; } @Override - public void overrideBuildPlanNotification(String projectKey, String buildPlanKey, VcsRepositoryUrl repositoryUrl) { + public void overrideBuildPlanNotification(String projectKey, String buildPlanKey, VcsRepositoryUri repositoryUri) { try { Document currentConfig = jenkinsJobService.getJobConfig(projectKey, buildPlanKey); Document newConfig = replaceNotificationUrlInJobConfig(currentConfig); @@ -80,8 +80,8 @@ public void overrideBuildPlanNotification(String projectKey, String buildPlanKey } @Override - public void deleteBuildTriggers(String projectKey, String buildPlanKey, VcsRepositoryUrl repositoryUrl) { - removeWebHook(repositoryUrl); + public void deleteBuildTriggers(String projectKey, String buildPlanKey, VcsRepositoryUri repositoryUri) { + removeWebHook(repositoryUri); if (projectKey != null && buildPlanKey != null) { try { @@ -97,7 +97,7 @@ public void deleteBuildTriggers(String projectKey, String buildPlanKey, VcsRepos } @Override - public void overrideBuildPlanRepository(String buildPlanKey, String name, String repositoryUrl, String defaultBranch) { + public void overrideBuildPlanRepository(String buildPlanKey, String name, String repositoryUri, String defaultBranch) { // not needed for Jenkins } @@ -109,7 +109,7 @@ public void overrideRepositoriesToCheckout(String buildPlanKey, List getPageableStudentParticipations( ProgrammingExerciseStudentParticipationRepository programmingExerciseStudentParticipationRepository, Pageable pageable) { - return programmingExerciseStudentParticipationRepository.findAllWithRepositoryUrlOrBuildPlanId(pageable); + return programmingExerciseStudentParticipationRepository.findAllWithRepositoryUriOrBuildPlanId(pageable); } @Override @@ -123,8 +123,8 @@ public boolean buildPlanExists(String projectKey, String buildPlanKey) { } @Override - public void removeWebHook(VcsRepositoryUrl repositoryUrl) { - final var repositoryPath = urlService.getRepositoryPathFromRepositoryUrl(repositoryUrl); + public void removeWebHook(VcsRepositoryUri repositoryUri) { + final var repositoryPath = uriService.getRepositoryPathFromRepositoryUri(repositoryUri); try { List hooks = gitlab.getProjectApi().getHooks(repositoryPath); @@ -153,7 +153,7 @@ else if (internalJenkinsUrl != null) { } } catch (GitLabApiException e) { - throw new GitLabException("Unable to remove webhook for " + repositoryUrl, e); + throw new GitLabException("Unable to remove webhook for " + repositoryUri, e); } } diff --git a/src/main/java/de/tum/in/www1/artemis/config/migration/entries/MigrationEntry20230808_203400.java b/src/main/java/de/tum/in/www1/artemis/config/migration/entries/MigrationEntry20230808_203400.java index 56d71f3e7560..23a3e33f5bf9 100644 --- a/src/main/java/de/tum/in/www1/artemis/config/migration/entries/MigrationEntry20230808_203400.java +++ b/src/main/java/de/tum/in/www1/artemis/config/migration/entries/MigrationEntry20230808_203400.java @@ -26,11 +26,11 @@ import de.tum.in.www1.artemis.config.migration.MigrationEntry; import de.tum.in.www1.artemis.domain.AuxiliaryRepository; import de.tum.in.www1.artemis.domain.ProgrammingExercise; -import de.tum.in.www1.artemis.domain.VcsRepositoryUrl; +import de.tum.in.www1.artemis.domain.VcsRepositoryUri; import de.tum.in.www1.artemis.domain.participation.*; import de.tum.in.www1.artemis.exception.ContinuousIntegrationException; import de.tum.in.www1.artemis.repository.*; -import de.tum.in.www1.artemis.service.UrlService; +import de.tum.in.www1.artemis.service.UriService; import de.tum.in.www1.artemis.service.connectors.vcs.VersionControlService; @Component @@ -62,7 +62,7 @@ public class MigrationEntry20230808_203400 extends MigrationEntry { private final Environment environment; - private final UrlService urlService = new UrlService(); + private final UriService uriService = new UriService(); private final CopyOnWriteArrayList errorList = new CopyOnWriteArrayList<>(); @@ -346,18 +346,18 @@ private List getAuxiliaryRepositories(Long exerciseId) { */ private void migrateTestRepository(AbstractBaseProgrammingExerciseParticipation participation) { var exercise = participation.getProgrammingExercise(); - if (exercise.getTestRepositoryUrl() == null) { + if (exercise.getTestRepositoryUri() == null) { /* - * when the test repository url is null, we don't have to migrate the test build plan, saving multiple API calls + * when the test repository uri is null, we don't have to migrate the test build plan, saving multiple API calls * this is also only needed for Jenkins, as Bamboo does not have a separate test build plan */ return; } try { - ciMigrationService.orElseThrow().removeWebHook(exercise.getVcsTestRepositoryUrl()); + ciMigrationService.orElseThrow().removeWebHook(exercise.getVcsTestRepositoryUri()); } catch (Exception e) { - log.warn("Failed to delete build triggers in test repository for exercise {} with test repository {}", exercise.getId(), exercise.getVcsTestRepositoryUrl(), e); + log.warn("Failed to delete build triggers in test repository for exercise {} with test repository {}", exercise.getId(), exercise.getVcsTestRepositoryUri(), e); errorList.add(participation); } } @@ -369,19 +369,19 @@ private void migrateTestRepository(AbstractBaseProgrammingExerciseParticipation * @param auxiliaryRepositories The auxiliary repositories to migrate */ private void migrateStudentBuildPlan(ProgrammingExerciseStudentParticipation participation, List auxiliaryRepositories) { - VcsRepositoryUrl repositoryUrl; + VcsRepositoryUri repositoryUri; ProgrammingExercise exercise = participation.getProgrammingExercise(); try { - repositoryUrl = new VcsRepositoryUrl(urlService.getPlainUrlFromRepositoryUrl(participation.getVcsRepositoryUrl())); + repositoryUri = new VcsRepositoryUri(uriService.getPlainUriFromRepositoryUri(participation.getVcsRepositoryUri())); } catch (URISyntaxException e) { log.warn("Failed to convert git url {} for studentParticipationId {} exerciseId {} with buildPlanId {}, will abort migration for this Participation", - participation.getVcsRepositoryUrl(), participation.getId(), exercise.getId(), participation.getBuildPlanId(), e); + participation.getVcsRepositoryUri(), participation.getId(), exercise.getId(), participation.getBuildPlanId(), e); errorList.add(participation); return; } try { - ciMigrationService.orElseThrow().overrideBuildPlanNotification(exercise.getProjectKey(), participation.getBuildPlanId(), repositoryUrl); + ciMigrationService.orElseThrow().overrideBuildPlanNotification(exercise.getProjectKey(), participation.getBuildPlanId(), repositoryUri); } catch (Exception e) { log.warn("Failed to migrate build plan notifications for studentParticipationId {} with buildPlanId {} of exerciseId {} ", participation.getId(), @@ -389,7 +389,7 @@ private void migrateStudentBuildPlan(ProgrammingExerciseStudentParticipation par errorList.add(participation); } try { - ciMigrationService.orElseThrow().deleteBuildTriggers(exercise.getProjectKey(), participation.getBuildPlanId(), repositoryUrl); + ciMigrationService.orElseThrow().deleteBuildTriggers(exercise.getProjectKey(), participation.getBuildPlanId(), repositoryUri); } catch (Exception e) { log.warn("Failed to delete build triggers for studentParticipationId {} with buildPlanId {} of exerciseId {} ", participation.getId(), participation.getBuildPlanId(), @@ -397,7 +397,7 @@ private void migrateStudentBuildPlan(ProgrammingExerciseStudentParticipation par errorList.add(participation); } try { - ciMigrationService.orElseThrow().overrideBuildPlanRepository(participation.getBuildPlanId(), ASSIGNMENT_REPO_NAME, String.valueOf(repositoryUrl), + ciMigrationService.orElseThrow().overrideBuildPlanRepository(participation.getBuildPlanId(), ASSIGNMENT_REPO_NAME, String.valueOf(repositoryUri), participation.getBranch()); } catch (Exception e) { @@ -406,7 +406,7 @@ private void migrateStudentBuildPlan(ProgrammingExerciseStudentParticipation par errorList.add(participation); } try { - ciMigrationService.orElseThrow().overrideBuildPlanRepository(participation.getBuildPlanId(), TEST_REPO_NAME, exercise.getTestRepositoryUrl(), + ciMigrationService.orElseThrow().overrideBuildPlanRepository(participation.getBuildPlanId(), TEST_REPO_NAME, exercise.getTestRepositoryUri(), participation.getBranch()); } catch (Exception e) { @@ -416,10 +416,10 @@ private void migrateStudentBuildPlan(ProgrammingExerciseStudentParticipation par } // NOTE: this handles an edge case with HASKELL exercises, where the solution repository is checked out in the student build plan try { - // we dont have the solution repository url in the student participation, so we have to get it from the repository service - var solutionRepositoryUrl = solutionProgrammingExerciseParticipationRepository.findByProgrammingExerciseId(exercise.getId()).get().getVcsRepositoryUrl(); + // we dont have the solution repository uri in the student participation, so we have to get it from the repository service + var solutionRepositoryUri = solutionProgrammingExerciseParticipationRepository.findByProgrammingExerciseId(exercise.getId()).get().getVcsRepositoryUri(); ciMigrationService.orElseThrow().overrideBuildPlanRepository(participation.getBuildPlanId(), SOLUTION_REPO_NAME, - urlService.getPlainUrlFromRepositoryUrl(solutionRepositoryUrl), participation.getBranch()); + uriService.getPlainUriFromRepositoryUri(solutionRepositoryUri), participation.getBranch()); } catch (Exception e) { log.warn("Failed to replace solution repository in student build plan for studentParticipationId {} with buildPlanId {} of exerciseId {} ", participation.getId(), @@ -428,7 +428,7 @@ private void migrateStudentBuildPlan(ProgrammingExerciseStudentParticipation par } for (var auxiliary : auxiliaryRepositories) { try { - ciMigrationService.orElseThrow().overrideBuildPlanRepository(participation.getBuildPlanId(), auxiliary.getName(), auxiliary.getRepositoryUrl(), + ciMigrationService.orElseThrow().overrideBuildPlanRepository(participation.getBuildPlanId(), auxiliary.getName(), auxiliary.getRepositoryUri(), participation.getBranch()); } catch (Exception e) { @@ -456,21 +456,21 @@ private void migrateStudentBuildPlan(ProgrammingExerciseStudentParticipation par private void migrateSolutionBuildPlan(AbstractBaseProgrammingExerciseParticipation participation, List auxiliaryRepositories) { ProgrammingExercise exercise = participation.getProgrammingExercise(); try { - ciMigrationService.orElseThrow().overrideBuildPlanNotification(exercise.getProjectKey(), exercise.getSolutionBuildPlanId(), exercise.getVcsSolutionRepositoryUrl()); + ciMigrationService.orElseThrow().overrideBuildPlanNotification(exercise.getProjectKey(), exercise.getSolutionBuildPlanId(), exercise.getVcsSolutionRepositoryUri()); } catch (Exception e) { log.warn("Failed to migrate solution build plan for exercise id {} with buildPlanId {}", exercise.getId(), exercise.getSolutionBuildPlanId(), e); errorList.add(participation); } try { - ciMigrationService.orElseThrow().deleteBuildTriggers(exercise.getProjectKey(), exercise.getSolutionBuildPlanId(), exercise.getVcsSolutionRepositoryUrl()); + ciMigrationService.orElseThrow().deleteBuildTriggers(exercise.getProjectKey(), exercise.getSolutionBuildPlanId(), exercise.getVcsSolutionRepositoryUri()); } catch (Exception e) { log.warn("Failed to delete solution build triggers for exercise {} with buildPlanId {}", exercise.getId(), exercise.getSolutionBuildPlanId(), e); errorList.add(participation); } try { - ciMigrationService.orElseThrow().overrideBuildPlanRepository(exercise.getSolutionBuildPlanId(), ASSIGNMENT_REPO_NAME, exercise.getSolutionRepositoryUrl(), + ciMigrationService.orElseThrow().overrideBuildPlanRepository(exercise.getSolutionBuildPlanId(), ASSIGNMENT_REPO_NAME, exercise.getSolutionRepositoryUri(), exercise.getBranch()); } catch (Exception e) { @@ -478,14 +478,14 @@ private void migrateSolutionBuildPlan(AbstractBaseProgrammingExerciseParticipati errorList.add(participation); } try { - ciMigrationService.orElseThrow().overrideBuildPlanRepository(exercise.getSolutionBuildPlanId(), TEST_REPO_NAME, exercise.getTestRepositoryUrl(), exercise.getBranch()); + ciMigrationService.orElseThrow().overrideBuildPlanRepository(exercise.getSolutionBuildPlanId(), TEST_REPO_NAME, exercise.getTestRepositoryUri(), exercise.getBranch()); } catch (Exception e) { log.warn("Failed to replace tests repository in solution build plan for exercise {} with buildPlanId {}", exercise.getId(), exercise.getSolutionBuildPlanId(), e); errorList.add(participation); } try { - ciMigrationService.orElseThrow().overrideBuildPlanRepository(participation.getBuildPlanId(), SOLUTION_REPO_NAME, exercise.getSolutionRepositoryUrl(), + ciMigrationService.orElseThrow().overrideBuildPlanRepository(participation.getBuildPlanId(), SOLUTION_REPO_NAME, exercise.getSolutionRepositoryUri(), exercise.getBranch()); } catch (Exception e) { @@ -495,7 +495,7 @@ private void migrateSolutionBuildPlan(AbstractBaseProgrammingExerciseParticipati } for (var auxiliary : auxiliaryRepositories) { try { - ciMigrationService.orElseThrow().overrideBuildPlanRepository(exercise.getSolutionBuildPlanId(), auxiliary.getName(), auxiliary.getRepositoryUrl(), + ciMigrationService.orElseThrow().overrideBuildPlanRepository(exercise.getSolutionBuildPlanId(), auxiliary.getName(), auxiliary.getRepositoryUri(), exercise.getBranch()); } catch (Exception e) { @@ -522,21 +522,21 @@ private void migrateSolutionBuildPlan(AbstractBaseProgrammingExerciseParticipati private void migrateTemplateBuildPlan(AbstractBaseProgrammingExerciseParticipation participation, List auxiliaryRepositories) { ProgrammingExercise exercise = participation.getProgrammingExercise(); try { - ciMigrationService.orElseThrow().overrideBuildPlanNotification(exercise.getProjectKey(), exercise.getTemplateBuildPlanId(), exercise.getVcsTemplateRepositoryUrl()); + ciMigrationService.orElseThrow().overrideBuildPlanNotification(exercise.getProjectKey(), exercise.getTemplateBuildPlanId(), exercise.getVcsTemplateRepositoryUri()); } catch (Exception e) { log.warn("Failed to migrate template build plan for exercise {} with buildPlanId {}", exercise.getId(), exercise.getTemplateBuildPlanId(), e); errorList.add(participation); } try { - ciMigrationService.orElseThrow().deleteBuildTriggers(exercise.getProjectKey(), exercise.getTemplateBuildPlanId(), exercise.getVcsTemplateRepositoryUrl()); + ciMigrationService.orElseThrow().deleteBuildTriggers(exercise.getProjectKey(), exercise.getTemplateBuildPlanId(), exercise.getVcsTemplateRepositoryUri()); } catch (Exception e) { log.warn("Failed to delete template build triggers for exercise {} with buildPlanId {}", exercise.getId(), exercise.getTemplateBuildPlanId(), e); errorList.add(participation); } try { - ciMigrationService.orElseThrow().overrideBuildPlanRepository(exercise.getTemplateBuildPlanId(), ASSIGNMENT_REPO_NAME, exercise.getTemplateRepositoryUrl(), + ciMigrationService.orElseThrow().overrideBuildPlanRepository(exercise.getTemplateBuildPlanId(), ASSIGNMENT_REPO_NAME, exercise.getTemplateRepositoryUri(), exercise.getBranch()); } catch (Exception e) { @@ -544,7 +544,7 @@ private void migrateTemplateBuildPlan(AbstractBaseProgrammingExerciseParticipati errorList.add(participation); } try { - ciMigrationService.orElseThrow().overrideBuildPlanRepository(exercise.getTemplateBuildPlanId(), TEST_REPO_NAME, exercise.getTestRepositoryUrl(), exercise.getBranch()); + ciMigrationService.orElseThrow().overrideBuildPlanRepository(exercise.getTemplateBuildPlanId(), TEST_REPO_NAME, exercise.getTestRepositoryUri(), exercise.getBranch()); } catch (Exception e) { log.warn("Failed to replace tests repository in template build plan for exercise {} with buildPlanId {}", exercise.getId(), exercise.getTemplateBuildPlanId(), e); @@ -552,10 +552,10 @@ private void migrateTemplateBuildPlan(AbstractBaseProgrammingExerciseParticipati } // NOTE: this handles an edge case with HASKELL exercises, where the solution repository is checked out in the student build plan try { - // we do not have the solution repository url in the template participation, so we have to get it from the repository service - var solutionRepositoryUrl = solutionProgrammingExerciseParticipationRepository.findByProgrammingExerciseId(exercise.getId()).get().getVcsRepositoryUrl(); + // we do not have the solution repository uri in the template participation, so we have to get it from the repository service + var solutionRepositoryUri = solutionProgrammingExerciseParticipationRepository.findByProgrammingExerciseId(exercise.getId()).get().getVcsRepositoryUri(); ciMigrationService.orElseThrow().overrideBuildPlanRepository(participation.getBuildPlanId(), SOLUTION_REPO_NAME, - urlService.getPlainUrlFromRepositoryUrl(solutionRepositoryUrl), exercise.getBranch()); + uriService.getPlainUriFromRepositoryUri(solutionRepositoryUri), exercise.getBranch()); } catch (Exception e) { log.warn("Failed to replace solution repository in template build plan for studentParticipationId {} with buildPlanId {} of exerciseId {} ", participation.getId(), @@ -564,7 +564,7 @@ private void migrateTemplateBuildPlan(AbstractBaseProgrammingExerciseParticipati } for (var auxiliary : auxiliaryRepositories) { try { - ciMigrationService.orElseThrow().overrideBuildPlanRepository(exercise.getTemplateBuildPlanId(), auxiliary.getName(), auxiliary.getRepositoryUrl(), + ciMigrationService.orElseThrow().overrideBuildPlanRepository(exercise.getTemplateBuildPlanId(), auxiliary.getName(), auxiliary.getRepositoryUri(), exercise.getBranch()); } catch (Exception e) { diff --git a/src/main/java/de/tum/in/www1/artemis/config/migration/entries/MigrationEntry20230920_181600.java b/src/main/java/de/tum/in/www1/artemis/config/migration/entries/MigrationEntry20230920_181600.java index 33820bd5f583..f8b7b4f02e21 100644 --- a/src/main/java/de/tum/in/www1/artemis/config/migration/entries/MigrationEntry20230920_181600.java +++ b/src/main/java/de/tum/in/www1/artemis/config/migration/entries/MigrationEntry20230920_181600.java @@ -21,10 +21,10 @@ import de.tum.in.www1.artemis.config.migration.MigrationEntry; import de.tum.in.www1.artemis.domain.ProgrammingExercise; -import de.tum.in.www1.artemis.domain.VcsRepositoryUrl; +import de.tum.in.www1.artemis.domain.VcsRepositoryUri; import de.tum.in.www1.artemis.domain.participation.*; import de.tum.in.www1.artemis.repository.*; -import de.tum.in.www1.artemis.service.UrlService; +import de.tum.in.www1.artemis.service.UriService; import de.tum.in.www1.artemis.service.connectors.vcs.VersionControlService; @Component @@ -52,7 +52,7 @@ public class MigrationEntry20230920_181600 extends MigrationEntry { private final Environment environment; - private final UrlService urlService = new UrlService(); + private final UriService uriService = new UriService(); private final CopyOnWriteArrayList errorList = new CopyOnWriteArrayList<>(); @@ -264,19 +264,19 @@ private void migrateStudents(List parti * @param participation The participation to migrate */ private void migrateStudentBuildPlan(ProgrammingExerciseStudentParticipation participation) { - VcsRepositoryUrl repositoryUrl; + VcsRepositoryUri repositoryUri; ProgrammingExercise exercise = participation.getProgrammingExercise(); try { - repositoryUrl = new VcsRepositoryUrl(urlService.getPlainUrlFromRepositoryUrl(participation.getVcsRepositoryUrl())); + repositoryUri = new VcsRepositoryUri(uriService.getPlainUriFromRepositoryUri(participation.getVcsRepositoryUri())); } catch (URISyntaxException e) { log.warn("Failed to convert git url {} for studentParticipationId {} exerciseId {} with buildPlanId {}, will abort migration for this Participation", - participation.getVcsRepositoryUrl(), participation.getId(), exercise.getId(), participation.getBuildPlanId(), e); + participation.getVcsRepositoryUri(), participation.getId(), exercise.getId(), participation.getBuildPlanId(), e); errorList.add(participation); return; } try { - ciMigrationService.orElseThrow().overrideBuildPlanNotification(exercise.getProjectKey(), participation.getBuildPlanId(), repositoryUrl); + ciMigrationService.orElseThrow().overrideBuildPlanNotification(exercise.getProjectKey(), participation.getBuildPlanId(), repositoryUri); } catch (Exception e) { log.warn("Failed to migrate build plan notifications for studentParticipationId {} with buildPlanId {} of exerciseId {} ", participation.getId(), @@ -293,7 +293,7 @@ private void migrateStudentBuildPlan(ProgrammingExerciseStudentParticipation par private void migrateSolutionBuildPlan(AbstractBaseProgrammingExerciseParticipation participation) { ProgrammingExercise exercise = participation.getProgrammingExercise(); try { - ciMigrationService.orElseThrow().overrideBuildPlanNotification(exercise.getProjectKey(), exercise.getSolutionBuildPlanId(), exercise.getVcsSolutionRepositoryUrl()); + ciMigrationService.orElseThrow().overrideBuildPlanNotification(exercise.getProjectKey(), exercise.getSolutionBuildPlanId(), exercise.getVcsSolutionRepositoryUri()); } catch (Exception e) { log.warn("Failed to migrate solution build plan for exercise id {} with buildPlanId {}", exercise.getId(), exercise.getSolutionBuildPlanId(), e); @@ -309,7 +309,7 @@ private void migrateSolutionBuildPlan(AbstractBaseProgrammingExerciseParticipati private void migrateTemplateBuildPlan(AbstractBaseProgrammingExerciseParticipation participation) { ProgrammingExercise exercise = participation.getProgrammingExercise(); try { - ciMigrationService.orElseThrow().overrideBuildPlanNotification(exercise.getProjectKey(), exercise.getTemplateBuildPlanId(), exercise.getVcsTemplateRepositoryUrl()); + ciMigrationService.orElseThrow().overrideBuildPlanNotification(exercise.getProjectKey(), exercise.getTemplateBuildPlanId(), exercise.getVcsTemplateRepositoryUri()); } catch (Exception e) { log.warn("Failed to migrate template build plan for exercise {} with buildPlanId {}", exercise.getId(), exercise.getTemplateBuildPlanId(), e); diff --git a/src/main/java/de/tum/in/www1/artemis/domain/Authority.java b/src/main/java/de/tum/in/www1/artemis/domain/Authority.java index 941fb4759b3f..86b78588bf58 100644 --- a/src/main/java/de/tum/in/www1/artemis/domain/Authority.java +++ b/src/main/java/de/tum/in/www1/artemis/domain/Authority.java @@ -27,15 +27,15 @@ public class Authority implements Serializable { @Serial private static final long serialVersionUID = 1L; - public static Authority ADMIN_AUTHORITY = new Authority(Role.ADMIN.getAuthority()); + public static final Authority ADMIN_AUTHORITY = new Authority(Role.ADMIN.getAuthority()); - public static Authority INSTRUCTOR_AUTHORITY = new Authority(Role.INSTRUCTOR.getAuthority()); + public static final Authority INSTRUCTOR_AUTHORITY = new Authority(Role.INSTRUCTOR.getAuthority()); - public static Authority EDITOR_AUTHORITY = new Authority(Role.EDITOR.getAuthority()); + public static final Authority EDITOR_AUTHORITY = new Authority(Role.EDITOR.getAuthority()); - public static Authority TA_AUTHORITY = new Authority(Role.TEACHING_ASSISTANT.getAuthority()); + public static final Authority TA_AUTHORITY = new Authority(Role.TEACHING_ASSISTANT.getAuthority()); - public static Authority USER_AUTHORITY = new Authority(Role.STUDENT.getAuthority()); + public static final Authority USER_AUTHORITY = new Authority(Role.STUDENT.getAuthority()); @NotNull @Size(max = 50) diff --git a/src/main/java/de/tum/in/www1/artemis/domain/AuxiliaryRepository.java b/src/main/java/de/tum/in/www1/artemis/domain/AuxiliaryRepository.java index 09176c47a323..474319f77af4 100644 --- a/src/main/java/de/tum/in/www1/artemis/domain/AuxiliaryRepository.java +++ b/src/main/java/de/tum/in/www1/artemis/domain/AuxiliaryRepository.java @@ -30,7 +30,7 @@ public class AuxiliaryRepository extends DomainObject { public static final int MAX_CHECKOUT_DIRECTORY_LENGTH = 100; @JsonIgnore - public static final int MAX_REPOSITORY_URL_LENGTH = 500; + public static final int MAX_REPOSITORY_URI_LENGTH = 500; @JsonIgnore public static final int MAX_DESCRIPTION_LENGTH = 500; @@ -49,9 +49,9 @@ public class AuxiliaryRepository extends DomainObject { @Column(name = "name") private String name; - @Size(max = MAX_REPOSITORY_URL_LENGTH) + @Size(max = MAX_REPOSITORY_URI_LENGTH) @Column(name = "repository_url") - private String repositoryUrl; + private String repositoryUri; /** * One programming exercise must not have multiple repositories @@ -69,12 +69,12 @@ public class AuxiliaryRepository extends DomainObject { @JsonIgnore private ProgrammingExercise exercise; - public String getRepositoryUrl() { - return repositoryUrl; + public String getRepositoryUri() { + return repositoryUri; } - public void setRepositoryUrl(String repositoryUrl) { - this.repositoryUrl = repositoryUrl; + public void setRepositoryUri(String repositoryUri) { + this.repositoryUri = repositoryUri; } public String getCheckoutDirectory() { @@ -122,21 +122,21 @@ public String getRepositoryName() { /** * Returns whether this auxiliary repository should be included in the exercise's build plan or not. - * The repository is included when the repository url and the checkout directory have appropriate + * The repository is included when the repository uri and the checkout directory have appropriate * values. * * @return true or false whether this repository should be included in the exercise build plan or not */ @JsonIgnore public boolean shouldBeIncludedInBuildPlan() { - return getCheckoutDirectory() != null && !getCheckoutDirectory().isBlank() && getRepositoryUrl() != null && !getRepositoryUrl().isBlank(); + return getCheckoutDirectory() != null && !getCheckoutDirectory().isBlank() && getRepositoryUri() != null && !getRepositoryUri().isBlank(); } /** - * Returns a copy of this auxiliary repository object without including the repository url. + * Returns a copy of this auxiliary repository object without including the repository uri. * This is used when importing auxiliary repositories from an old exercise to a new exercise. * - * @return A clone of this auxiliary repository object except the repository url + * @return A clone of this auxiliary repository object except the repository uri */ @JsonIgnore public AuxiliaryRepository cloneObjectForNewExercise() { @@ -148,35 +148,35 @@ public AuxiliaryRepository cloneObjectForNewExercise() { } /** - * Gets a URL of the repositoryUrl if there is one + * Gets a URL of the repositoryUri if there is one * - * @return a URL object of the repositoryUrl or null if there is no repositoryUrl + * @return a URL object of the repositoryUri or null if there is no repositoryUri */ @JsonIgnore - public VcsRepositoryUrl getVcsRepositoryUrl() { - String repositoryUrl = getRepositoryUrl(); - if (repositoryUrl == null || repositoryUrl.isEmpty()) { + public VcsRepositoryUri getVcsRepositoryUri() { + String repositoryUri = getRepositoryUri(); + if (repositoryUri == null || repositoryUri.isEmpty()) { return null; } try { - return new VcsRepositoryUrl(repositoryUrl); + return new VcsRepositoryUri(repositoryUri); } catch (URISyntaxException e) { - log.error("Malformed URI {} could not be used to instantiate VcsRepositoryUrl.", getRepositoryUrl(), e); + log.error("Malformed URI {} could not be used to instantiate VcsRepositoryUri.", getRepositoryUri(), e); } return null; } @Override public String toString() { - return "AuxiliaryRepository{id=%d, name='%s', checkoutDirectory='%s', repositoryUrl='%s', description='%s', exercise=%s}".formatted(getId(), getName(), - getCheckoutDirectory(), getRepositoryUrl(), getDescription(), exercise == null ? "null" : exercise.getId()); + return "AuxiliaryRepository{id=%d, name='%s', checkoutDirectory='%s', repositoryUri='%s', description='%s', exercise=%s}".formatted(getId(), getName(), + getCheckoutDirectory(), getRepositoryUri(), getDescription(), exercise == null ? "null" : exercise.getId()); } /** - * Used in Bamboo Service to map the name of an auxiliary repository to its repository url. + * Used in Bamboo Service to map the name of an auxiliary repository to its repository uri. */ - public record AuxRepoNameWithUrl(String name, VcsRepositoryUrl repositoryUrl) { + public record AuxRepoNameWithUri(String name, VcsRepositoryUri repositoryUri) { } public boolean containsEqualStringValues(AuxiliaryRepository other) { diff --git a/src/main/java/de/tum/in/www1/artemis/domain/NotificationSetting.java b/src/main/java/de/tum/in/www1/artemis/domain/NotificationSetting.java index eb5feafeef89..b8814dcd5608 100644 --- a/src/main/java/de/tum/in/www1/artemis/domain/NotificationSetting.java +++ b/src/main/java/de/tum/in/www1/artemis/domain/NotificationSetting.java @@ -1,7 +1,5 @@ package de.tum.in.www1.artemis.domain; -import java.util.Objects; - import javax.persistence.*; import org.hibernate.annotations.Cache; @@ -100,49 +98,4 @@ public boolean isPush() { public String toString() { return "NotificationSetting{" + ", settingId='" + settingId + '\'' + ", webapp=" + webapp + ", email=" + email + ", push=" + push + ", user=" + user + '}'; } - - @Override - public int hashCode() { - return Objects.hash(getSettingId(), getUser(), isWebapp(), isEmail()); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - DomainObject domainObject = (DomainObject) obj; - if (domainObject.getId() == null || getId() == null) { - return false; - } - boolean domainObjectCheck = Objects.equals(getId(), domainObject.getId()); - NotificationSetting providedSetting = (NotificationSetting) obj; - boolean userCheck = checkUser(this.user, providedSetting.user); - boolean settingIdCheck = checkSettingId(this.settingId, providedSetting.settingId); - return domainObjectCheck && userCheck && settingIdCheck && this.webapp == providedSetting.webapp && this.email == providedSetting.email - && this.push == providedSetting.push; - } - - private boolean checkUser(User thisUser, User providedUser) { - if (thisUser == null && providedUser == null) { - return true; - } - if (thisUser != null && providedUser != null) { - return thisUser.equals(providedUser); - } - return false; - } - - private boolean checkSettingId(String thisSettingId, String providedSettingId) { - if (thisSettingId == null && providedSettingId == null) { - return true; - } - if (thisSettingId != null) { - return thisSettingId.equals(providedSettingId); - } - return false; - } } diff --git a/src/main/java/de/tum/in/www1/artemis/domain/ProgrammingExercise.java b/src/main/java/de/tum/in/www1/artemis/domain/ProgrammingExercise.java index 83eaf8fac255..a5675882fc72 100644 --- a/src/main/java/de/tum/in/www1/artemis/domain/ProgrammingExercise.java +++ b/src/main/java/de/tum/in/www1/artemis/domain/ProgrammingExercise.java @@ -50,7 +50,7 @@ public String getType() { private static final Logger log = LoggerFactory.getLogger(ProgrammingExercise.class); @Column(name = "test_repository_url") - private String testRepositoryUrl; + private String testRepositoryUri; @OneToMany(mappedBy = "exercise", cascade = CascadeType.ALL, orphanRemoval = true) @JsonIgnoreProperties(value = "exercise", allowSetters = true) @@ -161,16 +161,16 @@ public String getType() { */ // jhipster-needle-entity-add-field - Jhipster will add fields here, do not remove @JsonIgnore - public String getTemplateRepositoryUrl() { + public String getTemplateRepositoryUri() { if (templateParticipation != null && Hibernate.isInitialized(templateParticipation)) { - return templateParticipation.getRepositoryUrl(); + return templateParticipation.getRepositoryUri(); } return null; } - public void setTemplateRepositoryUrl(String templateRepositoryUrl) { + public void setTemplateRepositoryUri(String templateRepositoryUri) { if (templateParticipation != null && Hibernate.isInitialized(templateParticipation)) { - this.templateParticipation.setRepositoryUrl(templateRepositoryUrl); + this.templateParticipation.setRepositoryUri(templateRepositoryUri); } } @@ -180,25 +180,25 @@ public void setTemplateRepositoryUrl(String templateRepositoryUrl) { * @return The URL of the solution repository as a String */ @JsonIgnore - public String getSolutionRepositoryUrl() { + public String getSolutionRepositoryUri() { if (solutionParticipation != null && Hibernate.isInitialized(solutionParticipation)) { - return solutionParticipation.getRepositoryUrl(); + return solutionParticipation.getRepositoryUri(); } return null; } - public void setSolutionRepositoryUrl(String solutionRepositoryUrl) { + public void setSolutionRepositoryUri(String solutionRepositoryUri) { if (solutionParticipation != null && Hibernate.isInitialized(solutionParticipation)) { - this.solutionParticipation.setRepositoryUrl(solutionRepositoryUrl); + this.solutionParticipation.setRepositoryUri(solutionRepositoryUri); } } - public void setTestRepositoryUrl(String testRepositoryUrl) { - this.testRepositoryUrl = testRepositoryUrl; + public void setTestRepositoryUri(String testRepositoryUri) { + this.testRepositoryUri = testRepositoryUri; } - public String getTestRepositoryUrl() { - return testRepositoryUrl; + public String getTestRepositoryUri() { + return testRepositoryUri; } public List getAuxiliaryRepositories() { @@ -461,43 +461,43 @@ public void setSubmissionPolicy(SubmissionPolicy submissionPolicy) { // jhipster-needle-entity-add-getters-setters - Jhipster will add getters and setters here, do not remove /** - * Gets a URL of the templateRepositoryUrl if there is one + * Gets a URL of the templateRepositoryUri if there is one * - * @return a URL object of the templateRepositoryUrl or null if there is no templateRepositoryUrl + * @return a URL object of the templateRepositoryUri or null if there is no templateRepositoryUri */ @JsonIgnore - public VcsRepositoryUrl getVcsTemplateRepositoryUrl() { - var templateRepositoryUrl = getTemplateRepositoryUrl(); - if (templateRepositoryUrl == null || templateRepositoryUrl.isEmpty()) { + public VcsRepositoryUri getVcsTemplateRepositoryUri() { + var templateRepositoryUri = getTemplateRepositoryUri(); + if (templateRepositoryUri == null || templateRepositoryUri.isEmpty()) { return null; } try { - return new VcsRepositoryUrl(templateRepositoryUrl); + return new VcsRepositoryUri(templateRepositoryUri); } catch (URISyntaxException e) { - log.warn("Cannot create URI for templateRepositoryUrl: {} due to the following error: {}", templateRepositoryUrl, e.getMessage()); + log.warn("Cannot create URI for templateRepositoryUri: {} due to the following error: {}", templateRepositoryUri, e.getMessage()); } return null; } /** - * Gets a URL of the solutionRepositoryUrl if there is one + * Gets a URL of the solutionRepositoryUri if there is one * - * @return a URL object of the solutionRepositoryUrl or null if there is no solutionRepositoryUrl + * @return a URL object of the solutionRepositoryUri or null if there is no solutionRepositoryUri */ @JsonIgnore - public VcsRepositoryUrl getVcsSolutionRepositoryUrl() { - var solutionRepositoryUrl = getSolutionRepositoryUrl(); - if (solutionRepositoryUrl == null || solutionRepositoryUrl.isEmpty()) { + public VcsRepositoryUri getVcsSolutionRepositoryUri() { + var solutionRepositoryUri = getSolutionRepositoryUri(); + if (solutionRepositoryUri == null || solutionRepositoryUri.isEmpty()) { return null; } try { - return new VcsRepositoryUrl(solutionRepositoryUrl); + return new VcsRepositoryUri(solutionRepositoryUri); } catch (URISyntaxException e) { - log.warn("Cannot create URI for solutionRepositoryUrl: {} due to the following error: {}", solutionRepositoryUrl, e.getMessage()); + log.warn("Cannot create URI for solutionRepositoryUri: {} due to the following error: {}", solutionRepositoryUri, e.getMessage()); } return null; } @@ -505,35 +505,35 @@ public VcsRepositoryUrl getVcsSolutionRepositoryUrl() { /** * Gets a URL of the testRepositoryURL if there is one * - * @return a URL object of the testRepositoryURl or null if there is no testRepositoryUrl + * @return a URL object of the testRepositoryURl or null if there is no testRepositoryUri */ @JsonIgnore - public VcsRepositoryUrl getVcsTestRepositoryUrl() { - if (testRepositoryUrl == null || testRepositoryUrl.isEmpty()) { + public VcsRepositoryUri getVcsTestRepositoryUri() { + if (testRepositoryUri == null || testRepositoryUri.isEmpty()) { return null; } try { - return new VcsRepositoryUrl(testRepositoryUrl); + return new VcsRepositoryUri(testRepositoryUri); } catch (URISyntaxException e) { - log.warn("Cannot create URI for testRepositoryUrl: {} due to the following error: {}", testRepositoryUrl, e.getMessage()); + log.warn("Cannot create URI for testRepositoryUri: {} due to the following error: {}", testRepositoryUri, e.getMessage()); } return null; } /** - * Returns the repository url for the given repository type. + * Returns the repository uri for the given repository type. * * @param repositoryType The repository type for which the url should be returned - * @return The repository url + * @return The repository uri */ @JsonIgnore - public VcsRepositoryUrl getRepositoryURL(RepositoryType repositoryType) { + public VcsRepositoryUri getRepositoryURL(RepositoryType repositoryType) { return switch (repositoryType) { - case TEMPLATE -> this.getVcsTemplateRepositoryUrl(); - case SOLUTION -> this.getVcsSolutionRepositoryUrl(); - case TESTS -> this.getVcsTestRepositoryUrl(); + case TEMPLATE -> this.getVcsTemplateRepositoryUri(); + case SOLUTION -> this.getVcsSolutionRepositoryUri(); + case TESTS -> this.getVcsTestRepositoryUri(); default -> throw new UnsupportedOperationException("Can retrieve URL for repository type " + repositoryType); }; } @@ -646,9 +646,9 @@ public void setTestwiseCoverageEnabled(Boolean testwiseCoverageEnabled) { */ @Override public void filterSensitiveInformation() { - setTemplateRepositoryUrl(null); - setSolutionRepositoryUrl(null); - setTestRepositoryUrl(null); + setTemplateRepositoryUri(null); + setSolutionRepositoryUri(null); + setTestRepositoryUri(null); setTemplateBuildPlanId(null); setSolutionBuildPlanId(null); super.filterSensitiveInformation(); @@ -732,7 +732,7 @@ private boolean checkForAssessedResult(Result result) { @Override public String toString() { - return "ProgrammingExercise{" + "id=" + getId() + ", templateRepositoryUrl='" + getTemplateRepositoryUrl() + "'" + ", solutionRepositoryUrl='" + getSolutionRepositoryUrl() + return "ProgrammingExercise{" + "id=" + getId() + ", templateRepositoryUri='" + getTemplateRepositoryUri() + "'" + ", solutionRepositoryUri='" + getSolutionRepositoryUri() + "'" + ", templateBuildPlanId='" + getTemplateBuildPlanId() + "'" + ", solutionBuildPlanId='" + getSolutionBuildPlanId() + "'" + ", publishBuildPlanUrl='" + isPublishBuildPlanUrl() + "'" + ", allowOnlineEditor='" + isAllowOnlineEditor() + "'" + ", programmingLanguage='" + getProgrammingLanguage() + "'" + ", packageName='" + getPackageName() + "'" + ", testCasesChanged='" + testCasesChanged + "'" + "}"; diff --git a/src/main/java/de/tum/in/www1/artemis/domain/Repository.java b/src/main/java/de/tum/in/www1/artemis/domain/Repository.java index c09135f772a6..7185c5f143ae 100644 --- a/src/main/java/de/tum/in/www1/artemis/domain/Repository.java +++ b/src/main/java/de/tum/in/www1/artemis/domain/Repository.java @@ -8,12 +8,13 @@ import org.eclipse.jgit.lib.BaseRepositoryBuilder; import de.tum.in.www1.artemis.domain.participation.ProgrammingExerciseParticipation; +import de.tum.in.www1.artemis.service.connectors.localvc.LocalVCRepositoryUri; /** * This class represents repositories cloned from the VC system to Artemis to then be used in the online editor. * These repositories are cloned from the remote VCS and are saved in the folder defined in the application properties at artemis.repo-clone-path. * Note: This class does not represent local VCS repositories. The local VCS is treated as a remote VCS in code (like Bitbucket and GitLab) and is represented by the - * {@link de.tum.in.www1.artemis.service.connectors.localvc.LocalVCRepositoryUrl} class. + * {@link LocalVCRepositoryUri} class. * Its repositories are saved as bare repositories in the folder defined in the application properties at artemis.version-control.local-vcs-repo-path. */ public class Repository extends org.eclipse.jgit.internal.storage.file.FileRepository { @@ -22,26 +23,26 @@ public class Repository extends org.eclipse.jgit.internal.storage.file.FileRepos private Path localPath; - private final VcsRepositoryUrl remoteRepositoryUrl; + private final VcsRepositoryUri remoteRepositoryUri; private Map filesAndFolders; private Collection files; - public Repository(File gitDir, VcsRepositoryUrl remoteRepositoryUrl) throws IOException { + public Repository(File gitDir, VcsRepositoryUri remoteRepositoryUri) throws IOException { super(gitDir); - this.remoteRepositoryUrl = remoteRepositoryUrl; + this.remoteRepositoryUri = remoteRepositoryUri; } - public Repository(String gitDir, VcsRepositoryUrl remoteRepositoryUrl) throws IOException { + public Repository(String gitDir, VcsRepositoryUri remoteRepositoryUri) throws IOException { super(gitDir); - this.remoteRepositoryUrl = remoteRepositoryUrl; + this.remoteRepositoryUri = remoteRepositoryUri; } - public Repository(BaseRepositoryBuilder options, Path localPath, VcsRepositoryUrl remoteRepositoryUrl) throws IOException { + public Repository(BaseRepositoryBuilder options, Path localPath, VcsRepositoryUri remoteRepositoryUri) throws IOException { super(options); this.localPath = localPath.normalize(); - this.remoteRepositoryUrl = remoteRepositoryUrl; + this.remoteRepositoryUri = remoteRepositoryUri; } /** @@ -97,7 +98,7 @@ public void closeBeforeDelete() { super.doClose(); } - public VcsRepositoryUrl getRemoteRepositoryUrl() { - return remoteRepositoryUrl; + public VcsRepositoryUri getRemoteRepositoryUri() { + return remoteRepositoryUri; } } diff --git a/src/main/java/de/tum/in/www1/artemis/domain/VcsRepositoryUrl.java b/src/main/java/de/tum/in/www1/artemis/domain/VcsRepositoryUri.java similarity index 90% rename from src/main/java/de/tum/in/www1/artemis/domain/VcsRepositoryUrl.java rename to src/main/java/de/tum/in/www1/artemis/domain/VcsRepositoryUri.java index 1828f83ae38f..18f34783593a 100644 --- a/src/main/java/de/tum/in/www1/artemis/domain/VcsRepositoryUrl.java +++ b/src/main/java/de/tum/in/www1/artemis/domain/VcsRepositoryUri.java @@ -4,28 +4,27 @@ import java.net.URISyntaxException; import java.util.Objects; -// TODO: we might want to rename this class to VcsRepositoryUri -public class VcsRepositoryUrl { +public class VcsRepositoryUri { protected String username; protected URI uri; - protected VcsRepositoryUrl() { + protected VcsRepositoryUri() { // NOTE: this constructor should not be used and only exists to prevent compile errors } // Create the url from a uriSpecString, e.g. https://ab123cd@bitbucket.ase.in.tum.de/scm/EIST2016RME/RMEXERCISE-ab123cd - public VcsRepositoryUrl(String uriSpecString) throws URISyntaxException { + public VcsRepositoryUri(String uriSpecString) throws URISyntaxException { this.uri = new URI(uriSpecString); } // Create the url from a file reference, e.g. C:/Users/Admin/AppData/Local/Temp/studentOriginRepo1644180397872264950 - public VcsRepositoryUrl(java.io.File file) { + public VcsRepositoryUri(java.io.File file) { this.uri = file.toURI(); } - public VcsRepositoryUrl withUser(final String username) { + public VcsRepositoryUri withUser(final String username) { this.username = username; return this; } @@ -41,7 +40,7 @@ public boolean equals(Object obj) { } // we explicitly allow subclasses (i.e. obj is a subclass of this) here (to avoid issues when comparing subclasses with the same url) // Note that this also includes the null check - if (!(obj instanceof VcsRepositoryUrl that)) { + if (!(obj instanceof VcsRepositoryUri that)) { return false; } return Objects.equals(username, that.username) && Objects.equals(uri, that.uri); @@ -58,7 +57,7 @@ public String toString() { return this.uri.toString(); } else { - return "VcsRepositoryUrl: empty"; + return "VcsRepositoryUri: empty"; } } @@ -75,7 +74,7 @@ public String toString() { * * @return the folderName as a string. */ - public String folderNameForRepositoryUrl() { + public String folderNameForRepositoryUri() { if ("file".equals(uri.getScheme())) { // Take the last element of the path final var segments = uri.getPath().split("/"); diff --git a/src/main/java/de/tum/in/www1/artemis/domain/hestia/ProgrammingExerciseSolutionEntry.java b/src/main/java/de/tum/in/www1/artemis/domain/hestia/ProgrammingExerciseSolutionEntry.java index aa377818a8bc..8f3425159657 100644 --- a/src/main/java/de/tum/in/www1/artemis/domain/hestia/ProgrammingExerciseSolutionEntry.java +++ b/src/main/java/de/tum/in/www1/artemis/domain/hestia/ProgrammingExerciseSolutionEntry.java @@ -1,7 +1,5 @@ package de.tum.in.www1.artemis.domain.hestia; -import java.util.Objects; - import javax.persistence.*; import org.hibernate.annotations.Cache; @@ -125,22 +123,6 @@ public void setTestCase(ProgrammingExerciseTestCase testCase) { this.testCase = testCase; } - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - if (!super.equals(obj)) { - return false; - } - ProgrammingExerciseSolutionEntry that = (ProgrammingExerciseSolutionEntry) obj; - return Objects.equals(filePath, that.filePath) && Objects.equals(previousLine, that.previousLine) && Objects.equals(line, that.line) - && Objects.equals(previousCode, that.previousCode) && Objects.equals(code, that.code); - } - @Override public String toString() { return "ProgrammingExerciseSolutionEntry{" + "id=" + getId() + '\'' + ", filePath='" + filePath + '\'' + ", previousLine=" + previousLine + ", line=" + line diff --git a/src/main/java/de/tum/in/www1/artemis/domain/hestia/ProgrammingExerciseTask.java b/src/main/java/de/tum/in/www1/artemis/domain/hestia/ProgrammingExerciseTask.java index 6f2674294f60..485154481fe3 100644 --- a/src/main/java/de/tum/in/www1/artemis/domain/hestia/ProgrammingExerciseTask.java +++ b/src/main/java/de/tum/in/www1/artemis/domain/hestia/ProgrammingExerciseTask.java @@ -1,7 +1,6 @@ package de.tum.in.www1.artemis.domain.hestia; import java.util.HashSet; -import java.util.Objects; import java.util.Set; import javax.persistence.*; @@ -79,19 +78,4 @@ public void setExercise(ProgrammingExercise exercise) { public String toString() { return "ProgrammingExerciseTask{" + "taskName='" + taskName + '\'' + ", testCases=" + testCases + '}'; } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - if (!super.equals(obj)) { - return false; - } - ProgrammingExerciseTask that = (ProgrammingExerciseTask) obj; - return Objects.equals(taskName, that.taskName) && Objects.equals(testCases, that.testCases); - } } diff --git a/src/main/java/de/tum/in/www1/artemis/domain/iris/message/IrisTextMessageContent.java b/src/main/java/de/tum/in/www1/artemis/domain/iris/message/IrisTextMessageContent.java index d502eea9aa49..8236ae2242e6 100644 --- a/src/main/java/de/tum/in/www1/artemis/domain/iris/message/IrisTextMessageContent.java +++ b/src/main/java/de/tum/in/www1/artemis/domain/iris/message/IrisTextMessageContent.java @@ -1,7 +1,5 @@ package de.tum.in.www1.artemis.domain.iris.message; -import java.util.Objects; - import javax.annotation.Nullable; import javax.persistence.Column; import javax.persistence.DiscriminatorValue; @@ -49,9 +47,4 @@ public void setTextContent(@Nullable String textContent) { public String toString() { return "IrisMessageContent{" + "message=" + (message == null ? "null" : message.getId()) + ", textContent='" + textContent + '\'' + '}'; } - - @Override - public boolean equals(Object obj) { - return super.equals(obj) && Objects.equals(this.textContent, ((IrisTextMessageContent) obj).textContent); - } } diff --git a/src/main/java/de/tum/in/www1/artemis/domain/participation/AbstractBaseProgrammingExerciseParticipation.java b/src/main/java/de/tum/in/www1/artemis/domain/participation/AbstractBaseProgrammingExerciseParticipation.java index fa70aecfaa31..cc669df8af94 100644 --- a/src/main/java/de/tum/in/www1/artemis/domain/participation/AbstractBaseProgrammingExerciseParticipation.java +++ b/src/main/java/de/tum/in/www1/artemis/domain/participation/AbstractBaseProgrammingExerciseParticipation.java @@ -17,18 +17,18 @@ public abstract class AbstractBaseProgrammingExerciseParticipation extends Parti @Column(name = "repository_url") @JsonView(QuizView.Before.class) - private String repositoryUrl; + private String repositoryUri; @Column(name = "build_plan_id") @JsonView(QuizView.Before.class) private String buildPlanId; - public String getRepositoryUrl() { - return repositoryUrl; + public String getRepositoryUri() { + return repositoryUri; } - public void setRepositoryUrl(String repositoryUrl) { - this.repositoryUrl = repositoryUrl; + public void setRepositoryUri(String repositoryUri) { + this.repositoryUri = repositoryUri; } public String getBuildPlanId() { @@ -62,6 +62,6 @@ public void filterSensitiveInformation() { @Override public String toString() { - return getClass().getSimpleName() + "{" + "id=" + getId() + ", repositoryUrl='" + getRepositoryUrl() + "'" + ", buildPlanId='" + getBuildPlanId() + "}"; + return getClass().getSimpleName() + "{" + "id=" + getId() + ", repositoryUri='" + getRepositoryUri() + "'" + ", buildPlanId='" + getBuildPlanId() + "}"; } } diff --git a/src/main/java/de/tum/in/www1/artemis/domain/participation/ProgrammingExerciseParticipation.java b/src/main/java/de/tum/in/www1/artemis/domain/participation/ProgrammingExerciseParticipation.java index cbf03dfcffe3..80dd8a4a295f 100644 --- a/src/main/java/de/tum/in/www1/artemis/domain/participation/ProgrammingExerciseParticipation.java +++ b/src/main/java/de/tum/in/www1/artemis/domain/participation/ProgrammingExerciseParticipation.java @@ -14,15 +14,15 @@ import de.tum.in.www1.artemis.domain.ProgrammingExercise; import de.tum.in.www1.artemis.domain.Result; -import de.tum.in.www1.artemis.domain.VcsRepositoryUrl; +import de.tum.in.www1.artemis.domain.VcsRepositoryUri; public interface ProgrammingExerciseParticipation extends ParticipationInterface { Logger log = LoggerFactory.getLogger(ProgrammingExerciseParticipation.class); - String getRepositoryUrl(); + String getRepositoryUri(); - void setRepositoryUrl(String repositoryUrl); + void setRepositoryUri(String repositoryUri); String getBuildPlanId(); @@ -41,36 +41,36 @@ public interface ProgrammingExerciseParticipation extends ParticipationInterface */ @Nullable @JsonInclude - default String getUserIndependentRepositoryUrl() { - if (getRepositoryUrl() == null) { + default String getUserIndependentRepositoryUri() { + if (getRepositoryUri() == null) { return null; } try { - URI repoUrl = new URI(getRepositoryUrl()); + URI repoUri = new URI(getRepositoryUri()); // Note: the following line reconstructs the URL without using the authority, it removes ’username@' before the host - return new URI(repoUrl.getScheme(), null, repoUrl.getHost(), repoUrl.getPort(), repoUrl.getPath(), null, null).toString(); + return new URI(repoUri.getScheme(), null, repoUri.getHost(), repoUri.getPort(), repoUri.getPath(), null, null).toString(); } catch (URISyntaxException e) { - log.debug("Cannot create user independent repository url from {} due to malformed URL exception", getRepositoryUrl(), e); + log.debug("Cannot create user independent repository uri from {} due to malformed URL exception", getRepositoryUri(), e); return null; } } /** - * @return the repository url of the programming exercise participation wrapped in an object + * @return the repository uri of the programming exercise participation wrapped in an object */ @JsonIgnore - default VcsRepositoryUrl getVcsRepositoryUrl() { - var repoUrl = getRepositoryUrl(); - if (repoUrl == null) { + default VcsRepositoryUri getVcsRepositoryUri() { + var repoUri = getRepositoryUri(); + if (repoUri == null) { return null; } try { - return new VcsRepositoryUrl(repoUrl); + return new VcsRepositoryUri(repoUri); } catch (URISyntaxException e) { - log.warn("Cannot create URI for repositoryUrl: {} due to the following error: {}", repoUrl, e.getMessage()); + log.warn("Cannot create URI for repositoryUri: {} due to the following error: {}", repoUri, e.getMessage()); } return null; } diff --git a/src/main/java/de/tum/in/www1/artemis/domain/participation/ProgrammingExerciseStudentParticipation.java b/src/main/java/de/tum/in/www1/artemis/domain/participation/ProgrammingExerciseStudentParticipation.java index 9005fa049173..e9ff258532d8 100644 --- a/src/main/java/de/tum/in/www1/artemis/domain/participation/ProgrammingExerciseStudentParticipation.java +++ b/src/main/java/de/tum/in/www1/artemis/domain/participation/ProgrammingExerciseStudentParticipation.java @@ -20,7 +20,7 @@ public class ProgrammingExerciseStudentParticipation extends StudentParticipatio @Column(name = "repository_url") @JsonView(QuizView.Before.class) - private String repositoryUrl; + private String repositoryUri; @Column(name = "build_plan_id") @JsonView(QuizView.Before.class) @@ -50,12 +50,12 @@ public ProgrammingExerciseStudentParticipation(String branch) { this.branch = branch; } - public String getRepositoryUrl() { - return repositoryUrl; + public String getRepositoryUri() { + return repositoryUri; } - public void setRepositoryUrl(String repositoryUrl) { - this.repositoryUrl = repositoryUrl; + public void setRepositoryUri(String repositoryUri) { + this.repositoryUri = repositoryUri; } public String getBuildPlanId() { @@ -115,7 +115,7 @@ public String getType() { @Override public String toString() { - return getClass().getSimpleName() + "{" + "id=" + getId() + ", repositoryUrl='" + getRepositoryUrl() + "'" + ", buildPlanId='" + getBuildPlanId() + "'" + return getClass().getSimpleName() + "{" + "id=" + getId() + ", repositoryUri='" + getRepositoryUri() + "'" + ", buildPlanId='" + getBuildPlanId() + "'" + ", initializationState='" + getInitializationState() + "'" + ", initializationDate='" + getInitializationDate() + "'" + ", individualDueDate=" + getIndividualDueDate() + "'" + ", presentationScore=" + getPresentationScore() + "}"; } diff --git a/src/main/java/de/tum/in/www1/artemis/domain/plagiarism/PlagiarismComparison.java b/src/main/java/de/tum/in/www1/artemis/domain/plagiarism/PlagiarismComparison.java index 96538da81170..a89b3230c30b 100644 --- a/src/main/java/de/tum/in/www1/artemis/domain/plagiarism/PlagiarismComparison.java +++ b/src/main/java/de/tum/in/www1/artemis/domain/plagiarism/PlagiarismComparison.java @@ -1,7 +1,6 @@ package de.tum.in.www1.artemis.domain.plagiarism; import java.io.File; -import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; @@ -172,25 +171,4 @@ public int compareTo(@NotNull PlagiarismComparison otherComparison) { public String toString() { return "PlagiarismComparison{" + "similarity=" + similarity + ", status=" + status + '}'; } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - if (!super.equals(obj)) { - return false; - } - - PlagiarismComparison that = (PlagiarismComparison) obj; - return Double.compare(that.similarity, similarity) == 0 && status == that.status; - } - - @Override - public int hashCode() { - return Objects.hash(super.hashCode(), getSimilarity(), getStatus()); - } } diff --git a/src/main/java/de/tum/in/www1/artemis/domain/plagiarism/PlagiarismSubmission.java b/src/main/java/de/tum/in/www1/artemis/domain/plagiarism/PlagiarismSubmission.java index 95cb416b4b7d..d09bf92fe670 100644 --- a/src/main/java/de/tum/in/www1/artemis/domain/plagiarism/PlagiarismSubmission.java +++ b/src/main/java/de/tum/in/www1/artemis/domain/plagiarism/PlagiarismSubmission.java @@ -197,25 +197,4 @@ public void setPlagiarismComparison(PlagiarismComparison plagiarismComparison public String toString() { return "PlagiarismSubmission{" + "submissionId=" + submissionId + ", studentLogin='" + studentLogin + '\'' + ", size=" + size + ", score=" + score + '}'; } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - if (!super.equals(obj)) { - return false; - } - PlagiarismSubmission that = (PlagiarismSubmission) obj; - return getSubmissionId() == that.getSubmissionId() && getSize() == that.getSize() && Objects.equals(getStudentLogin(), that.getStudentLogin()) - && Objects.equals(getScore(), that.getScore()); - } - - @Override - public int hashCode() { - return Objects.hash(super.hashCode(), getSubmissionId(), getStudentLogin(), getSize(), getScore()); - } } diff --git a/src/main/java/de/tum/in/www1/artemis/repository/ProgrammingExerciseStudentParticipationRepository.java b/src/main/java/de/tum/in/www1/artemis/repository/ProgrammingExerciseStudentParticipationRepository.java index baf225e7a5af..0ded947d3079 100644 --- a/src/main/java/de/tum/in/www1/artemis/repository/ProgrammingExerciseStudentParticipationRepository.java +++ b/src/main/java/de/tum/in/www1/artemis/repository/ProgrammingExerciseStudentParticipationRepository.java @@ -179,7 +179,7 @@ default Optional findStudentParticipati @Query(""" SELECT DISTINCT p FROM ProgrammingExerciseStudentParticipation p - WHERE p.buildPlanId IS NOT NULL or p.repositoryUrl IS NOT NULL + WHERE p.buildPlanId IS NOT NULL or p.repositoryUri IS NOT NULL """) - Page findAllWithRepositoryUrlOrBuildPlanId(Pageable pageable); + Page findAllWithRepositoryUriOrBuildPlanId(Pageable pageable); } diff --git a/src/main/java/de/tum/in/www1/artemis/service/ConsistencyCheckService.java b/src/main/java/de/tum/in/www1/artemis/service/ConsistencyCheckService.java index eb8e43df22f9..12e3864e3a08 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/ConsistencyCheckService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/ConsistencyCheckService.java @@ -79,17 +79,17 @@ private List checkVCSConsistency(ProgrammingExercise progra result.add(new ConsistencyErrorDTO(programmingExercise, ConsistencyErrorDTO.ErrorType.VCS_PROJECT_MISSING)); } else { - if (!versionControl.repositoryUrlIsValid(programmingExercise.getVcsTemplateRepositoryUrl())) { + if (!versionControl.repositoryUriIsValid(programmingExercise.getVcsTemplateRepositoryUri())) { result.add(new ConsistencyErrorDTO(programmingExercise, ConsistencyErrorDTO.ErrorType.TEMPLATE_REPO_MISSING)); } - if (!versionControl.repositoryUrlIsValid(programmingExercise.getVcsTestRepositoryUrl())) { + if (!versionControl.repositoryUriIsValid(programmingExercise.getVcsTestRepositoryUri())) { result.add(new ConsistencyErrorDTO(programmingExercise, ConsistencyErrorDTO.ErrorType.TEST_REPO_MISSING)); } - if (!versionControl.repositoryUrlIsValid(programmingExercise.getVcsSolutionRepositoryUrl())) { + if (!versionControl.repositoryUriIsValid(programmingExercise.getVcsSolutionRepositoryUri())) { result.add(new ConsistencyErrorDTO(programmingExercise, ConsistencyErrorDTO.ErrorType.SOLUTION_REPO_MISSING)); } for (var auxiliaryRepository : programmingExercise.getAuxiliaryRepositories()) { - if (!versionControl.repositoryUrlIsValid(auxiliaryRepository.getVcsRepositoryUrl())) { + if (!versionControl.repositoryUriIsValid(auxiliaryRepository.getVcsRepositoryUri())) { result.add(new ConsistencyErrorDTO(programmingExercise, ConsistencyErrorDTO.ErrorType.AUXILIARY_REPO_MISSING)); } } diff --git a/src/main/java/de/tum/in/www1/artemis/service/ExerciseService.java b/src/main/java/de/tum/in/www1/artemis/service/ExerciseService.java index 66f85b4c71a1..0d785d911c9f 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/ExerciseService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/ExerciseService.java @@ -418,7 +418,7 @@ public void filterForCourseDashboard(Exercise exercise, Set internalCiUrl, Optional internalVcs } /** - * Replaces the url of the vcs repository url to the internal url if it's + * Replaces the url of the vcs repository uri to the internal url if it's * defined. * - * @param vcsRepositoryUrl the vcs repository url - * @return the vcs repository url with the internal url + * @param vcsRepositoryUri the vcs repository uri + * @return the vcs repository uri with the internal url */ - public VcsRepositoryUrl toInternalVcsUrl(VcsRepositoryUrl vcsRepositoryUrl) { - if (vcsRepositoryUrl.getURI() == null) { + public VcsRepositoryUri toInternalVcsUrl(VcsRepositoryUri vcsRepositoryUri) { + if (vcsRepositoryUri.getURI() == null) { log.warn("Cannot replace url to internal url {} because the url is null.", internalVcsUrl); - return vcsRepositoryUrl; + return vcsRepositoryUri; } try { - String newInternalUrl = toInternalVcsUrl(vcsRepositoryUrl.getURI().toString()); - return new VcsRepositoryUrl(newInternalUrl); + String newInternalUrl = toInternalVcsUrl(vcsRepositoryUri.getURI().toString()); + return new VcsRepositoryUri(newInternalUrl); } catch (URISyntaxException e) { - log.warn("Cannot replace url {} to {}: {}. Falling back to original url.", vcsRepositoryUrl, internalVcsUrl, e.getMessage()); - return vcsRepositoryUrl; + log.warn("Cannot replace url {} to {}: {}. Falling back to original url.", vcsRepositoryUri, internalVcsUrl, e.getMessage()); + return vcsRepositoryUri; } } /** - * Replaces the url of the vcs repository url to the internal url if it's + * Replaces the url of the vcs repository uri to the internal url if it's * defined. * - * @param vcsRepositoryUrl the vcs repository url - * @return the vcs repository url with the internal url + * @param vcsRepositoryUri the vcs repository uri + * @return the vcs repository uri with the internal url */ - public String toInternalVcsUrl(String vcsRepositoryUrl) { + public String toInternalVcsUrl(String vcsRepositoryUri) { if (internalVcsUrl.isEmpty()) { - return vcsRepositoryUrl; + return vcsRepositoryUri; } - if (vcsRepositoryUrl == null) { + if (vcsRepositoryUri == null) { log.warn("Cannot replace url to internal url {} because the url is null.", internalVcsUrl); - return vcsRepositoryUrl; + return vcsRepositoryUri; } - return replaceUrl(vcsRepositoryUrl, internalVcsUrl.get()); + return replaceUrl(vcsRepositoryUri, internalVcsUrl.get()); } /** diff --git a/src/main/java/de/tum/in/www1/artemis/service/ParticipationService.java b/src/main/java/de/tum/in/www1/artemis/service/ParticipationService.java index 2755d00e1d7c..38bdb868ad92 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/ParticipationService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/ParticipationService.java @@ -50,7 +50,7 @@ public class ParticipationService { private final TeamRepository teamRepository; - private final UrlService urlService; + private final UriService uriService; private final ResultService resultService; @@ -69,7 +69,7 @@ public class ParticipationService { public ParticipationService(GitService gitService, Optional continuousIntegrationService, Optional versionControlService, BuildLogEntryService buildLogEntryService, ParticipationRepository participationRepository, StudentParticipationRepository studentParticipationRepository, ProgrammingExerciseStudentParticipationRepository programmingExerciseStudentParticipationRepository, ProgrammingExerciseRepository programmingExerciseRepository, - SubmissionRepository submissionRepository, TeamRepository teamRepository, UrlService urlService, ResultService resultService, + SubmissionRepository submissionRepository, TeamRepository teamRepository, UriService uriService, ResultService resultService, CoverageReportRepository coverageReportRepository, BuildLogStatisticsEntryRepository buildLogStatisticsEntryRepository, ParticipantScoreRepository participantScoreRepository, StudentScoreRepository studentScoreRepository, TeamScoreRepository teamScoreRepository, Optional localCISharedBuildJobQueueService) { @@ -83,7 +83,7 @@ public ParticipationService(GitService gitService, Optional usersToRemove = new HashSet<>(existingTeam.getStudents()); usersToRemove.removeAll(updatedTeam.getStudents()); - usersToRemove.forEach(user -> versionControlService.orElseThrow().removeMemberFromRepository(participation.getVcsRepositoryUrl(), user)); + usersToRemove.forEach(user -> versionControlService.orElseThrow().removeMemberFromRepository(participation.getVcsRepositoryUri(), user)); // Users in the updated team that were not yet part of the existing team need to be added Set usersToAdd = new HashSet<>(updatedTeam.getStudents()); usersToAdd.removeAll(existingTeam.getStudents()); usersToAdd.forEach( - user -> versionControlService.orElseThrow().addMemberToRepository(participation.getVcsRepositoryUrl(), user, VersionControlRepositoryPermission.REPO_WRITE)); + user -> versionControlService.orElseThrow().addMemberToRepository(participation.getVcsRepositoryUri(), user, VersionControlRepositoryPermission.REPO_WRITE)); }); } diff --git a/src/main/java/de/tum/in/www1/artemis/service/UrlService.java b/src/main/java/de/tum/in/www1/artemis/service/UriService.java similarity index 52% rename from src/main/java/de/tum/in/www1/artemis/service/UrlService.java rename to src/main/java/de/tum/in/www1/artemis/service/UriService.java index 28013aa2f141..fe625ffe3b8a 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/UrlService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/UriService.java @@ -7,58 +7,58 @@ import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; -import de.tum.in.www1.artemis.domain.VcsRepositoryUrl; +import de.tum.in.www1.artemis.domain.VcsRepositoryUri; import de.tum.in.www1.artemis.exception.VersionControlException; @Service -public class UrlService { +public class UriService { - private final Logger log = LoggerFactory.getLogger(UrlService.class); + private final Logger log = LoggerFactory.getLogger(UriService.class); /** - * Gets the repository slug from the given repository URL, see {@link #getRepositorySlugFromUrl} + * Gets the repository slug from the given repository URI, see {@link #getRepositorySlugFromUri} * - * @param repositoryUrl The repository url object + * @param repositoryUri The repository uri object * @return The repository slug - * @throws VersionControlException if the URL is invalid and no repository slug could be extracted + * @throws VersionControlException if the URI is invalid and no repository slug could be extracted */ - public String getRepositorySlugFromRepositoryUrl(VcsRepositoryUrl repositoryUrl) throws VersionControlException { - return getRepositorySlugFromUrl(repositoryUrl.getURI()); + public String getRepositorySlugFromRepositoryUri(VcsRepositoryUri repositoryUri) throws VersionControlException { + return getRepositorySlugFromUri(repositoryUri.getURI()); } /** - * Gets the repository slug from the given repository URL string, see {@link #getRepositorySlugFromUrl} + * Gets the repository slug from the given repository URI string, see {@link #getRepositorySlugFromUri} * - * @param repositoryUrl The repository url as string + * @param repositoryUri The repository uri as string * @return The repository slug - * @throws VersionControlException if the URL is invalid and no repository slug could be extracted + * @throws VersionControlException if the URI is invalid and no repository slug could be extracted */ - public String getRepositorySlugFromRepositoryUrlString(String repositoryUrl) throws VersionControlException { + public String getRepositorySlugFromRepositoryUriString(String repositoryUri) throws VersionControlException { try { - return getRepositorySlugFromUrl(new URI(repositoryUrl)); + return getRepositorySlugFromUri(new URI(repositoryUri)); } catch (URISyntaxException e) { - log.error("Cannot get repository slug from repository url string {}", repositoryUrl, e); - throw new VersionControlException("Repository URL is not a git URL! Can't get repository slug for " + repositoryUrl); + log.error("Cannot get repository slug from repository uri string {}", repositoryUri, e); + throw new VersionControlException("Repository URI is not a git URI! Can't get repository slug for " + repositoryUri); } } /** - * Gets the repository slug from the given URL + * Gets the repository slug from the given URI * Example 1: https://ga42xab@bitbucket.ase.in.tum.de/scm/EIST2016RME/RMEXERCISE-ga42xab.git --> RMEXERCISE-ga42xab * Example 2: https://ga63fup@repobruegge.in.tum.de/scm/EIST2016RME/RMEXERCISE-ga63fup.git --> RMEXERCISE-ga63fup * Example 3: https://artemistest2gitlab.ase.in.tum.de/TESTADAPTER/testadapter-exercise.git --> testadapter-exercise * Example 4: https://turdiu@artemistest2gitlab.ase.in.tum.de/FTCSCAGRADING1/ftcscagrading1-turdiu.git --> ftcscagrading1-turdiu * - * @param url The complete repository url (including protocol, host and the complete path) - * @return The repository slug, i.e. the part of the url that identifies the repository (not the project) without .git in the end - * @throws VersionControlException if the URL is invalid and no repository slug could be extracted + * @param uri The complete repository uri (including protocol, host and the complete path) + * @return The repository slug, i.e. the part of the uri that identifies the repository (not the project) without .git in the end + * @throws VersionControlException if the URI is invalid and no repository slug could be extracted */ - private String getRepositorySlugFromUrl(URI url) throws VersionControlException { - // split the URL path in components using the separator "/" - final var pathComponents = url.getPath().split("/"); + private String getRepositorySlugFromUri(URI uri) throws VersionControlException { + // split the URI path in components using the separator "/" + final var pathComponents = uri.getPath().split("/"); if (pathComponents.length < 2) { - throw new VersionControlException("Repository URL is not a git URL! Can't get repository slug for " + url); + throw new VersionControlException("Repository URI is not a git URI! Can't get repository slug for " + uri); } // Note: pathComponents[0] = "" because the path always starts with "/" // take the last element @@ -72,30 +72,30 @@ private String getRepositorySlugFromUrl(URI url) throws VersionControlException } /** - * Gets the project key + repository slug from the given repository URL, ee {@link #getRepositoryPathFromUrl} + * Gets the project key + repository slug from the given repository URI, ee {@link #getRepositoryPathFromUri} * - * @param repositoryUrl The repository url object + * @param repositoryUri The repository uri object * @return / - * @throws VersionControlException if the URL is invalid and no project key could be extracted + * @throws VersionControlException if the URI is invalid and no project key could be extracted */ - public String getRepositoryPathFromRepositoryUrl(VcsRepositoryUrl repositoryUrl) throws VersionControlException { - return getRepositoryPathFromUrl(repositoryUrl.getURI()); + public String getRepositoryPathFromRepositoryUri(VcsRepositoryUri repositoryUri) throws VersionControlException { + return getRepositoryPathFromUri(repositoryUri.getURI()); } /** - * Gets the project key + repository slug from the given URL - * + * Gets the project key + repository slug from the given URI + *

* Example: https://artemistest2gitlab.ase.in.tum.de/TESTADAPTER/testadapter-exercise.git --> TESTADAPTER/testadapter-exercise * - * @param url The complete repository url (including protocol, host and the complete path) + * @param uri The complete repository uri (including protocol, host and the complete path) * @return / - * @throws VersionControlException if the URL is invalid and no project key could be extracted + * @throws VersionControlException if the URI is invalid and no project key could be extracted */ - private String getRepositoryPathFromUrl(URI url) throws VersionControlException { - // split the URL path in components using the separator "/" - final var pathComponents = url.getPath().split("/"); + private String getRepositoryPathFromUri(URI uri) throws VersionControlException { + // split the URI path in components using the separator "/" + final var pathComponents = uri.getPath().split("/"); if (pathComponents.length < 2) { - throw new VersionControlException("Repository URL is not a git URL! Can't get repository slug for " + url); + throw new VersionControlException("Repository URI is not a git URI! Can't get repository slug for " + uri); } // Note: pathComponents[0] = "" because the path always starts with "/" final var last = pathComponents.length - 1; @@ -103,32 +103,32 @@ private String getRepositoryPathFromUrl(URI url) throws VersionControlException } /** - * Gets the project key from the given repository URL, see {@link #getProjectKeyFromUrl} + * Gets the project key from the given repository URI, see {@link #getProjectKeyFromUri} * - * @param repositoryUrl The repository url object + * @param repositoryUri The repository uri object * @return The project key - * @throws VersionControlException if the URL is invalid and no project key could be extracted + * @throws VersionControlException if the URI is invalid and no project key could be extracted */ - public String getProjectKeyFromRepositoryUrl(VcsRepositoryUrl repositoryUrl) throws VersionControlException { - return getProjectKeyFromUrl(repositoryUrl.getURI()); + public String getProjectKeyFromRepositoryUri(VcsRepositoryUri repositoryUri) throws VersionControlException { + return getProjectKeyFromUri(repositoryUri.getURI()); } /** - * Gets the project key from the given URL + * Gets the project key from the given URI * * Examples: * https://ga42xab@bitbucket.ase.in.tum.de/scm/EIST2016RME/RMEXERCISE-ga42xab.git --> EIST2016RME * http://localhost:8080/git/TESTCOURSE1TESTEX1/testcourse1testex1-student1.git --> TESTCOURSE1TESTEX1 * - * @param url The complete repository url (including protocol, host and the complete path) + * @param uri The complete repository uri (including protocol, host and the complete path) * @return The project key - * @throws VersionControlException if the URL is invalid and no project key could be extracted + * @throws VersionControlException if the URI is invalid and no project key could be extracted */ - private String getProjectKeyFromUrl(URI url) throws VersionControlException { - // split the URL path in components using the separator "/" - final var pathComponents = url.getPath().split("/"); + private String getProjectKeyFromUri(URI uri) throws VersionControlException { + // split the URI path in components using the separator "/" + final var pathComponents = uri.getPath().split("/"); if (pathComponents.length <= 2) { - throw new VersionControlException("No project key could be found for " + url); + throw new VersionControlException("No project key could be found for " + uri); } // Note: pathComponents[0] = "" because the path always starts with "/" var projectKey = pathComponents[1]; @@ -140,15 +140,15 @@ private String getProjectKeyFromUrl(URI url) throws VersionControlException { } /** - * Gets the plain URL from the given repository URL, + * Gets the plain URI from the given repository URI, * https://ga42xab@bitbucket.ase.in.tum.de/scm/EIST2016RME/RMEXERCISE-ga42xab.git --> https://bitbucket.ase.in.tum.de/scm/EIST2016RME/RMEXERCISE-ga42xab.git * - * @param repositoryUrl The repository url object - * @return The plain URL - * @throws VersionControlException if the URL is invalid and no plain URL could be extracted + * @param repositoryUri The repository uri object + * @return The plain URI + * @throws VersionControlException if the URI is invalid and no plain URI could be extracted */ - public String getPlainUrlFromRepositoryUrl(VcsRepositoryUrl repositoryUrl) throws VersionControlException { - var uri = repositoryUrl.getURI(); + public String getPlainUriFromRepositoryUri(VcsRepositoryUri repositoryUri) throws VersionControlException { + var uri = repositoryUri.getURI(); try { var updatedUri = new URI(uri.getScheme(), null, uri.getHost(), uri.getPort(), uri.getPath(), null, uri.getFragment()); return updatedUri.toString(); diff --git a/src/main/java/de/tum/in/www1/artemis/service/connectors/GitService.java b/src/main/java/de/tum/in/www1/artemis/service/connectors/GitService.java index 01a406c909f5..1925b33a327a 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/connectors/GitService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/connectors/GitService.java @@ -54,7 +54,7 @@ import de.tum.in.www1.artemis.service.FileService; import de.tum.in.www1.artemis.service.ProfileService; import de.tum.in.www1.artemis.service.ZipFileService; -import de.tum.in.www1.artemis.service.connectors.localvc.LocalVCRepositoryUrl; +import de.tum.in.www1.artemis.service.connectors.localvc.LocalVCRepositoryUri; import de.tum.in.www1.artemis.web.rest.dto.CommitInfoDTO; import de.tum.in.www1.artemis.web.rest.errors.EntityNotFoundException; @@ -254,37 +254,37 @@ private boolean useSsh() { // password is optional and will only be applied if the ssh private key was encrypted using a password } - private String getGitUriAsString(VcsRepositoryUrl vcsRepositoryUrl) throws URISyntaxException { - return getGitUri(vcsRepositoryUrl).toString(); + private String getGitUriAsString(VcsRepositoryUri vcsRepositoryUri) throws URISyntaxException { + return getGitUri(vcsRepositoryUri).toString(); } /** - * Get the URI for a {@link VcsRepositoryUrl}. This either retrieves the SSH URI, if SSH is used, the HTTP(S) URI, or the path to the repository's folder if the local VCS is + * Get the URI for a {@link VcsRepositoryUri}. This either retrieves the SSH URI, if SSH is used, the HTTP(S) URI, or the path to the repository's folder if the local VCS is * used. * This method is for internal use (getting the URI for cloning the repository into the Artemis file system). * For Bitbucket and GitLab, the URI is the same internally as the one that is used by the students to clone the repository using their local Git client. * For the local VCS however, the repository is cloned from the folder defined in the environment variable "artemis.version-control.local-vcs-repo-path". * - * @param vcsRepositoryUrl the {@link VcsRepositoryUrl} for which to get the URI + * @param vcsRepositoryUri the {@link VcsRepositoryUri} for which to get the URI * @return the URI (SSH, HTTP(S), or local path) * @throws URISyntaxException if SSH is used and the SSH URI could not be retrieved. */ - private URI getGitUri(VcsRepositoryUrl vcsRepositoryUrl) throws URISyntaxException { + private URI getGitUri(VcsRepositoryUri vcsRepositoryUri) throws URISyntaxException { if (profileService.isLocalVcsCi()) { - // Create less generic LocalVCRepositoryUrl out of VcsRepositoryUrl. - LocalVCRepositoryUrl localVCRepositoryUrl = new LocalVCRepositoryUrl(vcsRepositoryUrl.toString(), gitUrl); + // Create less generic LocalVCRepositoryUri out of VcsRepositoryUri. + LocalVCRepositoryUri localVCRepositoryUri = new LocalVCRepositoryUri(vcsRepositoryUri.toString(), gitUrl); String localVCBasePath = environment.getProperty("artemis.version-control.local-vcs-repo-path"); - return localVCRepositoryUrl.getLocalRepositoryPath(localVCBasePath).toUri(); + return localVCRepositoryUri.getLocalRepositoryPath(localVCBasePath).toUri(); } - return useSsh() ? getSshUri(vcsRepositoryUrl) : vcsRepositoryUrl.getURI(); + return useSsh() ? getSshUri(vcsRepositoryUri) : vcsRepositoryUri.getURI(); } - private URI getSshUri(VcsRepositoryUrl vcsRepositoryUrl) throws URISyntaxException { + private URI getSshUri(VcsRepositoryUri vcsRepositoryUri) throws URISyntaxException { URI templateUri = new URI(sshUrlTemplate.orElseThrow()); // Example Bitbucket: ssh://git@bitbucket.ase.in.tum.de:7999/se2021w07h02/se2021w07h02-ga27yox.git // Example Gitlab: ssh://git@gitlab.ase.in.tum.de:2222/se2021w07h02/se2021w07h02-ga27yox.git - final var repositoryUri = vcsRepositoryUrl.getURI(); - // Bitbucket repository urls (until now mainly used with username and password authentication) include "/scm" in the url, which cannot be used in ssh urls, + final var repositoryUri = vcsRepositoryUri.getURI(); + // Bitbucket repository uris (until now mainly used with username and password authentication) include "/scm" in the url, which cannot be used in ssh urls, // therefore we need to replace it here final var path = repositoryUri.getPath().replace("/scm", ""); return new URI(templateUri.getScheme(), templateUri.getUserInfo(), templateUri.getHost(), templateUri.getPort(), path, null, repositoryUri.getFragment()); @@ -313,8 +313,8 @@ public Repository getOrCheckoutRepository(ProgrammingExerciseParticipation parti * @throws GitException if the same repository is attempted to be cloned multiple times. */ public Repository getOrCheckoutRepository(ProgrammingExerciseParticipation participation, String targetPath) throws GitAPIException, GitException { - var repoUrl = participation.getVcsRepositoryUrl(); - Repository repository = getOrCheckoutRepository(repoUrl, targetPath, true); + var repoUri = participation.getVcsRepositoryUri(); + Repository repository = getOrCheckoutRepository(repoUri, targetPath, true); repository.setParticipation(participation); return repository; } @@ -333,46 +333,46 @@ public Repository getOrCheckoutRepository(ProgrammingExerciseParticipation parti * @throws InvalidPathException if the repository could not be checked out Because it contains unmappable characters. */ public Repository getOrCheckoutRepositoryForJPlag(ProgrammingExerciseParticipation participation, String targetPath) throws GitAPIException, InvalidPathException { - var repoUrl = participation.getVcsRepositoryUrl(); - String repoFolderName = repoUrl.folderNameForRepositoryUrl(); + var repoUri = participation.getVcsRepositoryUri(); + String repoFolderName = repoUri.folderNameForRepositoryUri(); // Replace the exercise name in the repository folder name with the participation ID. // This is necessary to be able to refer back to the correct participation after the JPlag detection run. String updatedRepoFolderName = repoFolderName.replaceAll("/[a-zA-Z0-9]*-", "/" + participation.getId() + "-"); Path localPath = Path.of(targetPath, updatedRepoFolderName); - Repository repository = getOrCheckoutRepository(repoUrl, localPath, true); + Repository repository = getOrCheckoutRepository(repoUri, localPath, true); repository.setParticipation(participation); return repository; } /** - * Get the local repository for a given remote repository URL. If the local repo does not exist yet, it will be checked out. + * Get the local repository for a given remote repository URI. If the local repo does not exist yet, it will be checked out. * Saves the repo in the default path * - * @param repoUrl The remote repository. + * @param repoUri The remote repository. * @param pullOnGet Pull from the remote on the checked out repository, if it does not need to be cloned. * @return the repository if it could be checked out. * @throws GitAPIException if the repository could not be checked out. */ - public Repository getOrCheckoutRepository(VcsRepositoryUrl repoUrl, boolean pullOnGet) throws GitAPIException { - return getOrCheckoutRepository(repoUrl, repoClonePath, pullOnGet); + public Repository getOrCheckoutRepository(VcsRepositoryUri repoUri, boolean pullOnGet) throws GitAPIException { + return getOrCheckoutRepository(repoUri, repoClonePath, pullOnGet); } /** - * Get the local repository for a given remote repository URL. If the local repo does not exist yet, it will be checked out. + * Get the local repository for a given remote repository URI. If the local repo does not exist yet, it will be checked out. * - * @param repoUrl The remote repository. + * @param repoUri The remote repository. * @param targetPath path where the repo is located on disk * @param pullOnGet Pull from the remote on the checked out repository, if it does not need to be cloned. * @return the repository if it could be checked out. * @throws GitAPIException if the repository could not be checked out. * @throws GitException if the same repository is attempted to be cloned multiple times. */ - public Repository getOrCheckoutRepository(VcsRepositoryUrl repoUrl, String targetPath, boolean pullOnGet) throws GitAPIException, GitException { - Path localPath = getLocalPathOfRepo(targetPath, repoUrl); - return getOrCheckoutRepository(repoUrl, localPath, pullOnGet); + public Repository getOrCheckoutRepository(VcsRepositoryUri repoUri, String targetPath, boolean pullOnGet) throws GitAPIException, GitException { + Path localPath = getLocalPathOfRepo(targetPath, repoUri); + return getOrCheckoutRepository(repoUri, localPath, pullOnGet); } /** @@ -393,52 +393,52 @@ public Repository checkoutRepositoryAtCommit(Repository repository, String commi } /** - * Get the local repository for a given remote repository URL. + * Get the local repository for a given remote repository URI. *

* If the local repo does not exist yet, it will be checked out. * After retrieving the repository, the commit for the given hash will be checked out. * - * @param vcsRepositoryUrl the url of the remote repository + * @param vcsRepositoryUri the url of the remote repository * @param commitHash the hash of the commit to checkout * @param pullOnGet pull from the remote on the checked out repository, if it does not need to be cloned * @return the repository if it could be checked out * @throws GitAPIException if the repository could not be checked out */ - public Repository checkoutRepositoryAtCommit(VcsRepositoryUrl vcsRepositoryUrl, String commitHash, boolean pullOnGet) throws GitAPIException { - var repository = getOrCheckoutRepository(vcsRepositoryUrl, pullOnGet); + public Repository checkoutRepositoryAtCommit(VcsRepositoryUri vcsRepositoryUri, String commitHash, boolean pullOnGet) throws GitAPIException { + var repository = getOrCheckoutRepository(vcsRepositoryUri, pullOnGet); return checkoutRepositoryAtCommit(repository, commitHash); } /** - * Get the local repository for a given remote repository URL. If the local repo does not exist yet, it will be checked out. + * Get the local repository for a given remote repository URI. If the local repo does not exist yet, it will be checked out. * - * @param repoUrl The remote repository. + * @param repoUri The remote repository. * @param pullOnGet Pull from the remote on the checked out repository, if it does not need to be cloned. * @param defaultBranch The default branch of the target repository. * @return the repository if it could be checked out. * @throws GitAPIException if the repository could not be checked out. * @throws GitException if the same repository is attempted to be cloned multiple times. */ - public Repository getOrCheckoutRepository(VcsRepositoryUrl repoUrl, boolean pullOnGet, String defaultBranch) throws GitAPIException, GitException { - Path localPath = getLocalPathOfRepo(repoClonePath, repoUrl); - return getOrCheckoutRepository(repoUrl, repoUrl, localPath, pullOnGet, defaultBranch); + public Repository getOrCheckoutRepository(VcsRepositoryUri repoUri, boolean pullOnGet, String defaultBranch) throws GitAPIException, GitException { + Path localPath = getLocalPathOfRepo(repoClonePath, repoUri); + return getOrCheckoutRepository(repoUri, repoUri, localPath, pullOnGet, defaultBranch); } - public Repository getOrCheckoutRepositoryIntoTargetDirectory(VcsRepositoryUrl repoUrl, VcsRepositoryUrl targetUrl, boolean pullOnGet) + public Repository getOrCheckoutRepositoryIntoTargetDirectory(VcsRepositoryUri repoUri, VcsRepositoryUri targetUrl, boolean pullOnGet) throws GitAPIException, GitException, InvalidPathException { Path localPath = getDefaultLocalPathOfRepo(targetUrl); - return getOrCheckoutRepository(repoUrl, targetUrl, localPath, pullOnGet); + return getOrCheckoutRepository(repoUri, targetUrl, localPath, pullOnGet); } - public Repository getOrCheckoutRepository(VcsRepositoryUrl repoUrl, Path localPath, boolean pullOnGet) throws GitAPIException, GitException, InvalidPathException { - return getOrCheckoutRepository(repoUrl, repoUrl, localPath, pullOnGet); + public Repository getOrCheckoutRepository(VcsRepositoryUri repoUri, Path localPath, boolean pullOnGet) throws GitAPIException, GitException, InvalidPathException { + return getOrCheckoutRepository(repoUri, repoUri, localPath, pullOnGet); } /** - * Get the local repository for a given remote repository URL. If the local repo does not exist yet, it will be checked out. + * Get the local repository for a given remote repository URI. If the local repo does not exist yet, it will be checked out. * - * @param sourceRepoUrl The source remote repository. - * @param targetRepoUrl The target remote repository. + * @param sourceRepoUri The source remote repository. + * @param targetRepoUri The target remote repository. * @param localPath The local path to clone the repository to. * @param pullOnGet Pull from the remote on the checked out repository, if it does not need to be cloned. * @return the repository if it could be checked out. @@ -446,16 +446,16 @@ public Repository getOrCheckoutRepository(VcsRepositoryUrl repoUrl, Path localPa * @throws GitException if the same repository is attempted to be cloned multiple times. * @throws InvalidPathException if the repository could not be checked out Because it contains unmappable characters. */ - public Repository getOrCheckoutRepository(VcsRepositoryUrl sourceRepoUrl, VcsRepositoryUrl targetRepoUrl, Path localPath, boolean pullOnGet) + public Repository getOrCheckoutRepository(VcsRepositoryUri sourceRepoUri, VcsRepositoryUri targetRepoUri, Path localPath, boolean pullOnGet) throws GitAPIException, GitException, InvalidPathException { - return getOrCheckoutRepository(sourceRepoUrl, targetRepoUrl, localPath, pullOnGet, defaultBranch); + return getOrCheckoutRepository(sourceRepoUri, targetRepoUri, localPath, pullOnGet, defaultBranch); } /** - * Get the local repository for a given remote repository URL. If the local repo does not exist yet, it will be checked out. + * Get the local repository for a given remote repository URI. If the local repo does not exist yet, it will be checked out. * - * @param sourceRepoUrl The source remote repository. - * @param targetRepoUrl The target remote repository. + * @param sourceRepoUri The source remote repository. + * @param targetRepoUri The target remote repository. * @param localPath The local path to clone the repository to. * @param pullOnGet Pull from the remote on the checked out repository, if it does not need to be cloned. * @param defaultBranch The default branch of the target repository @@ -464,11 +464,11 @@ public Repository getOrCheckoutRepository(VcsRepositoryUrl sourceRepoUrl, VcsRep * @throws GitException if the same repository is attempted to be cloned multiple times. * @throws InvalidPathException if the repository could not be checked out Because it contains unmappable characters. */ - public Repository getOrCheckoutRepository(VcsRepositoryUrl sourceRepoUrl, VcsRepositoryUrl targetRepoUrl, Path localPath, boolean pullOnGet, String defaultBranch) + public Repository getOrCheckoutRepository(VcsRepositoryUri sourceRepoUri, VcsRepositoryUri targetRepoUri, Path localPath, boolean pullOnGet, String defaultBranch) throws GitAPIException, GitException, InvalidPathException { // First try to just retrieve the git repository from our server, as it might already be checked out. - // If the sourceRepoUrl differs from the targetRepoUrl, we attempt to clone the source repo into the target directory - Repository repository = getExistingCheckedOutRepositoryByLocalPath(localPath, targetRepoUrl, defaultBranch); + // If the sourceRepoUri differs from the targetRepoUri, we attempt to clone the source repo into the target directory + Repository repository = getExistingCheckedOutRepositoryByLocalPath(localPath, targetRepoUri, defaultBranch); // Note: in case the actual git repository in the file system is corrupt (e.g. by accident), we will get an exception here // the exception will then delete the folder, so that the next attempt would be successful. @@ -495,7 +495,7 @@ public Repository getOrCheckoutRepository(VcsRepositoryUrl sourceRepoUrl, VcsRep // Clone repository. try { - var gitUriAsString = getGitUriAsString(sourceRepoUrl); + var gitUriAsString = getGitUriAsString(sourceRepoUri); log.debug("Cloning from {} to {}", gitUriAsString, localPath); cloneInProgressOperations.put(localPath, localPath); // make sure the directory to copy into is empty @@ -515,7 +515,7 @@ public Repository getOrCheckoutRepository(VcsRepositoryUrl sourceRepoUrl, VcsRep // make sure that cloneInProgress is released cloneInProgressOperations.remove(localPath); } - return getExistingCheckedOutRepositoryByLocalPath(localPath, targetRepoUrl, defaultBranch); + return getExistingCheckedOutRepositoryByLocalPath(localPath, targetRepoUri, defaultBranch); } } @@ -553,11 +553,11 @@ private void waitUntilPathNotBusy(final Path localPath) throws CanceledException * Checks whether the repository is cached. * This method does only support repositories that use the repoClonePath which is set in the application-artemis.yml file! * - * @param repositoryUrl the url of the repository + * @param repositoryUri the url of the repository * @return returns true if the repository is already cached */ - public boolean isRepositoryCached(VcsRepositoryUrl repositoryUrl) { - Path localPath = getLocalPathOfRepo(repoClonePath, repositoryUrl); + public boolean isRepositoryCached(VcsRepositoryUri repositoryUri) { + Path localPath = getLocalPathOfRepo(repoClonePath, repositoryUri); if (localPath == null) { return false; } @@ -568,15 +568,15 @@ public boolean isRepositoryCached(VcsRepositoryUrl repositoryUrl) { /** * Combine all commits of the given repository into one. * - * @param repoUrl of the repository to combine. + * @param repoUri of the repository to combine. * @throws GitAPIException If the checkout fails */ - public void combineAllCommitsOfRepositoryIntoOne(VcsRepositoryUrl repoUrl) throws GitAPIException { - Repository exerciseRepository = getOrCheckoutRepository(repoUrl, true); + public void combineAllCommitsOfRepositoryIntoOne(VcsRepositoryUri repoUri) throws GitAPIException { + Repository exerciseRepository = getOrCheckoutRepository(repoUri, true); combineAllCommitsIntoInitialCommit(exerciseRepository); } - public Path getDefaultLocalPathOfRepo(VcsRepositoryUrl targetUrl) { + public Path getDefaultLocalPathOfRepo(VcsRepositoryUri targetUrl) { return getLocalPathOfRepo(repoClonePath, targetUrl); } @@ -587,11 +587,11 @@ public Path getDefaultLocalPathOfRepo(VcsRepositoryUrl targetUrl) { * @param targetUrl url of the repository * @return path of the local file system */ - public Path getLocalPathOfRepo(String targetPath, VcsRepositoryUrl targetUrl) { + public Path getLocalPathOfRepo(String targetPath, VcsRepositoryUri targetUrl) { if (targetUrl == null) { return null; } - return Path.of(targetPath.replaceAll("^\\." + Pattern.quote(java.io.File.separator), ""), targetUrl.folderNameForRepositoryUrl()); + return Path.of(targetPath.replaceAll("^\\." + Pattern.quote(java.io.File.separator), ""), targetUrl.folderNameForRepositoryUri()); } /** @@ -599,11 +599,11 @@ public Path getLocalPathOfRepo(String targetPath, VcsRepositoryUrl targetUrl) { * from cachedRepositories. Side effect: This method caches retrieved repositories in a HashMap, so continuous retrievals can be avoided (reduces load). * * @param localPath to git repo on server. - * @param remoteRepositoryUrl the remote repository url for the git repository, will be added to the Repository object for later use, can be null + * @param remoteRepositoryUri the remote repository uri for the git repository, will be added to the Repository object for later use, can be null * @return the git repository in the localPath or **null** if it does not exist on the server. */ - public Repository getExistingCheckedOutRepositoryByLocalPath(@NotNull Path localPath, @Nullable VcsRepositoryUrl remoteRepositoryUrl) { - return getExistingCheckedOutRepositoryByLocalPath(localPath, remoteRepositoryUrl, defaultBranch); + public Repository getExistingCheckedOutRepositoryByLocalPath(@NotNull Path localPath, @Nullable VcsRepositoryUri remoteRepositoryUri) { + return getExistingCheckedOutRepositoryByLocalPath(localPath, remoteRepositoryUri, defaultBranch); } /** @@ -611,11 +611,11 @@ public Repository getExistingCheckedOutRepositoryByLocalPath(@NotNull Path local * from cachedRepositories. Side effect: This method caches retrieved repositories in a HashMap, so continuous retrievals can be avoided (reduces load). * * @param localPath to git repo on server. - * @param remoteRepositoryUrl the remote repository url for the git repository, will be added to the Repository object for later use, can be null + * @param remoteRepositoryUri the remote repository uri for the git repository, will be added to the Repository object for later use, can be null * @param defaultBranch the name of the branch that should be used as default branch * @return the git repository in the localPath or **null** if it does not exist on the server. */ - public Repository getExistingCheckedOutRepositoryByLocalPath(@NotNull Path localPath, @Nullable VcsRepositoryUrl remoteRepositoryUrl, String defaultBranch) { + public Repository getExistingCheckedOutRepositoryByLocalPath(@NotNull Path localPath, @Nullable VcsRepositoryUri remoteRepositoryUri, String defaultBranch) { try { // Check if there is a folder with the provided path of the git repository. if (!Files.exists(localPath)) { @@ -636,7 +636,7 @@ public Repository getExistingCheckedOutRepositoryByLocalPath(@NotNull Path local FileRepositoryBuilder builder = new FileRepositoryBuilder(); builder.setGitDir(gitPath.toFile()).setInitialBranch(defaultBranch).readEnvironment().findGitDir().setup(); // scan environment GIT_* variables - Repository repository = createRepository(localPath, remoteRepositoryUrl, defaultBranch, builder); + Repository repository = createRepository(localPath, remoteRepositoryUri, defaultBranch, builder); RefUpdate refUpdate = repository.getRefDatabase().newUpdate(Constants.HEAD, false); refUpdate.setForceUpdate(true); @@ -656,16 +656,16 @@ public Repository getExistingCheckedOutRepositoryByLocalPath(@NotNull Path local * Creates a new Repository with the given parameters and saves the Repository's StoredConfig. * * @param localPath The local path of the repository. - * @param remoteRepositoryUrl The remote repository url for the git repository. + * @param remoteRepositoryUri The remote repository uri for the git repository. * @param defaultBranch The default branch of the repository. * @param builder The FileRepositoryBuilder. * @return The created Repository. * @throws IOException if the configuration file cannot be accessed. */ @NotNull - private static Repository createRepository(Path localPath, VcsRepositoryUrl remoteRepositoryUrl, String defaultBranch, FileRepositoryBuilder builder) throws IOException { + private static Repository createRepository(Path localPath, VcsRepositoryUri remoteRepositoryUri, String defaultBranch, FileRepositoryBuilder builder) throws IOException { // Create the JGit repository object - Repository repository = new Repository(builder, localPath, remoteRepositoryUrl); + Repository repository = new Repository(builder, localPath, remoteRepositoryUri); // disable auto garbage collection because it can lead to problems (especially with deleting local repositories) // see https://stackoverflow.com/questions/45266021/java-jgit-files-delete-fails-to-delete-a-file-but-file-delete-succeeds // and https://git-scm.com/docs/git-gc for an explanation of the parameter @@ -737,15 +737,15 @@ public void commitAndPush(Repository repo, String message, boolean emptyCommit, * The content to be copied then gets pushed to the new repo. * * @param targetRepo Local target repo - * @param targetRepoUrl URI of targets repo + * @param targetRepoUri URI of targets repo * @param oldBranch default branch that was used when the exercise was created (might differ from the default branch of a participation) * @throws GitAPIException if the repo could not be pushed */ - public void pushSourceToTargetRepo(Repository targetRepo, VcsRepositoryUrl targetRepoUrl, String oldBranch) throws GitAPIException { + public void pushSourceToTargetRepo(Repository targetRepo, VcsRepositoryUri targetRepoUri, String oldBranch) throws GitAPIException { try (Git git = new Git(targetRepo)) { // overwrite the old remote uri with the target uri - git.remoteSetUrl().setRemoteName(REMOTE_NAME).setRemoteUri(new URIish(getGitUriAsString(targetRepoUrl))).call(); - log.debug("pushSourceToTargetRepo -> Push {}", targetRepoUrl.getURI()); + git.remoteSetUrl().setRemoteName(REMOTE_NAME).setRemoteUri(new URIish(getGitUriAsString(targetRepoUri))).call(); + log.debug("pushSourceToTargetRepo -> Push {}", targetRepoUri.getURI()); if (!defaultBranch.equals(oldBranch)) { targetRepo.getConfig().unsetSection(ConfigConstants.CONFIG_BRANCH_SECTION, oldBranch); @@ -804,19 +804,19 @@ public void fetchAll(Repository repo) throws GitAPIException { } /** - * Change the remote repository url to the currently used authentication mechanism (either ssh or https) + * Change the remote repository uri to the currently used authentication mechanism (either ssh or https) * * @param repo the git repository for which the remote url should be change */ private void setRemoteUrl(Repository repo) { - if (repo == null || repo.getRemoteRepositoryUrl() == null) { + if (repo == null || repo.getRemoteRepositoryUri() == null) { log.warn("Cannot set remoteUrl because it is null!"); return; } // Note: we reset the remote url, because it might have changed from https to ssh or ssh to https try { var existingRemoteUrl = repo.getConfig().getString(ConfigConstants.CONFIG_REMOTE_SECTION, REMOTE_NAME, "url"); - var newRemoteUrl = getGitUriAsString(repo.getRemoteRepositoryUrl()); + var newRemoteUrl = getGitUriAsString(repo.getRemoteRepositoryUri()); if (!Objects.equals(newRemoteUrl, existingRemoteUrl)) { log.info("Replace existing remote url {} with new remote url {}", existingRemoteUrl, newRemoteUrl); repo.getConfig().setString(ConfigConstants.CONFIG_REMOTE_SECTION, REMOTE_NAME, "url", newRemoteUrl); @@ -903,7 +903,7 @@ public void resetToOriginHead(Repository repo) { reset(repo, "origin/" + originHead); } catch (GitAPIException | JGitInternalException ex) { - log.error("Cannot fetch/hard reset the repo {} with url {} to origin/HEAD due to the following exception", repo.getLocalPath(), repo.getRemoteRepositoryUrl(), ex); + log.error("Cannot fetch/hard reset the repo {} with url {} to origin/HEAD due to the following exception", repo.getLocalPath(), repo.getRemoteRepositoryUri(), ex); } } @@ -922,18 +922,18 @@ public void switchBackToDefaultBranchHead(Repository repository) throws GitAPIEx /** * Get last commit hash from HEAD * - * @param repoUrl to get the latest hash from. + * @param repoUri to get the latest hash from. * @return the latestHash of the given repo. * @throws EntityNotFoundException if retrieving the latestHash from the git repo failed. */ - public ObjectId getLastCommitHash(VcsRepositoryUrl repoUrl) throws EntityNotFoundException { - if (repoUrl == null || repoUrl.getURI() == null) { + public ObjectId getLastCommitHash(VcsRepositoryUri repoUri) throws EntityNotFoundException { + if (repoUri == null || repoUri.getURI() == null) { return null; } // Get HEAD ref of repo without cloning it locally try { - log.debug("getLastCommitHash {}", repoUrl); - var headRef = lsRemoteCommand().setRemote(getGitUriAsString(repoUrl)).callAsMap().get(Constants.HEAD); + log.debug("getLastCommitHash {}", repoUri); + var headRef = lsRemoteCommand().setRemote(getGitUriAsString(repoUri)).callAsMap().get(Constants.HEAD); if (headRef == null) { return null; @@ -942,7 +942,7 @@ public ObjectId getLastCommitHash(VcsRepositoryUrl repoUrl) throws EntityNotFoun return headRef.getObjectId(); } catch (GitAPIException | URISyntaxException ex) { - throw new EntityNotFoundException("Could not retrieve the last commit hash for repoUrl " + repoUrl + " due to the following exception: " + ex); + throw new EntityNotFoundException("Could not retrieve the last commit hash for repoUri " + repoUri + " due to the following exception: " + ex); } } @@ -1002,7 +1002,7 @@ public void combineAllStudentCommits(Repository repository, ProgrammingExercise try (Git studentGit = new Git(repository)) { setRemoteUrl(repository); // Get last commit hash from template repo - ObjectId latestHash = getLastCommitHash(programmingExercise.getVcsTemplateRepositoryUrl()); + ObjectId latestHash = getLastCommitHash(programmingExercise.getVcsTemplateRepositoryUri()); if (latestHash == null) { // Template Repository is somehow empty. Should never happen @@ -1050,7 +1050,7 @@ public void anonymizeStudentCommits(Repository repository, ProgrammingExercise p String headName = "HEAD"; // Get last commit hash from template repo - ObjectId latestHash = getLastCommitHash(programmingExercise.getVcsTemplateRepositoryUrl()); + ObjectId latestHash = getLastCommitHash(programmingExercise.getVcsTemplateRepositoryUri()); if (latestHash == null) { // Template Repository is somehow empty. Should never happen @@ -1267,15 +1267,15 @@ public void deleteLocalRepository(Repository repository) throws IOException { } /** - * Deletes a local repository folder for a repoUrl. + * Deletes a local repository folder for a repoUri. * - * @param repoUrl url of the repository. + * @param repoUri url of the repository. */ - public void deleteLocalRepository(VcsRepositoryUrl repoUrl) { + public void deleteLocalRepository(VcsRepositoryUri repoUri) { try { - if (repoUrl != null && repositoryAlreadyExists(repoUrl)) { + if (repoUri != null && repositoryAlreadyExists(repoUri)) { // We need to close the possibly still open repository otherwise an IOException will be thrown on Windows - Repository repo = getOrCheckoutRepository(repoUrl, false); + Repository repo = getOrCheckoutRepository(repoUri, false); deleteLocalRepository(repo); } } @@ -1357,11 +1357,11 @@ public Path zipFiles(Path contentRootPath, String zipFilename, String zipDir, @N /** * Checks if repo was already checked out and is present on disk * - * @param repoUrl URL of the remote repository. + * @param repoUri URL of the remote repository. * @return True if repo exists on disk */ - public boolean repositoryAlreadyExists(VcsRepositoryUrl repoUrl) { - Path localPath = getDefaultLocalPathOfRepo(repoUrl); + public boolean repositoryAlreadyExists(VcsRepositoryUri repoUri) { + Path localPath = getDefaultLocalPathOfRepo(repoUri); return Files.exists(localPath); } @@ -1406,16 +1406,16 @@ public > C authenticate(TransportCommand command) } /** - * Checkout a repository and get the git log for a given repository url. + * Checkout a repository and get the git log for a given repository uri. * - * @param vcsRepositoryUrl the repository url for which the git log should be retrieved + * @param vcsRepositoryUri the repository uri for which the git log should be retrieved * @return a list of commit info DTOs containing author, timestamp, commit message, and hash * @throws GitAPIException if an error occurs while retrieving the git log */ - public List getCommitInfos(VcsRepositoryUrl vcsRepositoryUrl) throws GitAPIException { + public List getCommitInfos(VcsRepositoryUri vcsRepositoryUri) throws GitAPIException { List commitInfos = new ArrayList<>(); - try (var repo = getOrCheckoutRepository(vcsRepositoryUrl, true); var git = new Git(repo)) { + try (var repo = getOrCheckoutRepository(vcsRepositoryUri, true); var git = new Git(repo)) { var commits = git.log().call(); commits.forEach(commit -> { var commitInfo = CommitInfoDTO.of(commit); diff --git a/src/main/java/de/tum/in/www1/artemis/service/connectors/aeolus/AeolusBuildPlanService.java b/src/main/java/de/tum/in/www1/artemis/service/connectors/aeolus/AeolusBuildPlanService.java index 21a139f553d2..9e35aee24e34 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/connectors/aeolus/AeolusBuildPlanService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/connectors/aeolus/AeolusBuildPlanService.java @@ -24,7 +24,7 @@ import com.google.gson.Gson; import de.tum.in.www1.artemis.domain.AuxiliaryRepository; -import de.tum.in.www1.artemis.domain.VcsRepositoryUrl; +import de.tum.in.www1.artemis.domain.VcsRepositoryUri; import de.tum.in.www1.artemis.domain.enumeration.AeolusTarget; import de.tum.in.www1.artemis.domain.enumeration.ProgrammingLanguage; import de.tum.in.www1.artemis.exception.ContinuousIntegrationBuildPlanException; @@ -118,29 +118,29 @@ public String publishBuildPlan(Windfile windfile, AeolusTarget target) { * @param programmingLanguage the programming language of the exercise * @param branch the branch of the exercise * @param checkoutSolutionRepository whether the solution repository should be checked out (only used in OCAML and Haskell exercises) - * @param repositoryUrl the url of the assignment repository - * @param testRepositoryUrl the url of the test repository - * @param solutionRepositoryUrl the url of the solution repository + * @param repositoryUri the uri of the assignment repository + * @param testRepositoryUri the uri of the test repository + * @param solutionRepositoryUri the uri of the solution repository * @param auxiliaryRepositories List of auxiliary repositories to be included in the build plan * @return a map of repositories used in Aeolus to create the checkout task in the custom build plan */ public Map createRepositoryMapForWindfile(ProgrammingLanguage programmingLanguage, String branch, boolean checkoutSolutionRepository, - VcsRepositoryUrl repositoryUrl, VcsRepositoryUrl testRepositoryUrl, VcsRepositoryUrl solutionRepositoryUrl, - List auxiliaryRepositories) { + VcsRepositoryUri repositoryUri, VcsRepositoryUri testRepositoryUri, VcsRepositoryUri solutionRepositoryUri, + List auxiliaryRepositories) { if (bambooInternalUrlService.isEmpty()) { throw new ContinuousIntegrationBuildPlanException("Internal URL service for Bamboo is not configured"); } Map repositoryMap = new HashMap<>(); - repositoryMap.put(ASSIGNMENT_REPO_NAME, new AeolusRepository(bambooInternalUrlService.get().toInternalVcsUrl(repositoryUrl).toString(), branch, + repositoryMap.put(ASSIGNMENT_REPO_NAME, new AeolusRepository(bambooInternalUrlService.get().toInternalVcsUrl(repositoryUri).toString(), branch, ContinuousIntegrationService.RepositoryCheckoutPath.ASSIGNMENT.forProgrammingLanguage(programmingLanguage))); if (checkoutSolutionRepository) { - repositoryMap.put(SOLUTION_REPO_NAME, new AeolusRepository(bambooInternalUrlService.get().toInternalVcsUrl(solutionRepositoryUrl).toString(), branch, + repositoryMap.put(SOLUTION_REPO_NAME, new AeolusRepository(bambooInternalUrlService.get().toInternalVcsUrl(solutionRepositoryUri).toString(), branch, ContinuousIntegrationService.RepositoryCheckoutPath.SOLUTION.forProgrammingLanguage(programmingLanguage))); } - repositoryMap.put(TEST_REPO_NAME, new AeolusRepository(bambooInternalUrlService.get().toInternalVcsUrl(testRepositoryUrl).toString(), branch, + repositoryMap.put(TEST_REPO_NAME, new AeolusRepository(bambooInternalUrlService.get().toInternalVcsUrl(testRepositoryUri).toString(), branch, ContinuousIntegrationService.RepositoryCheckoutPath.TEST.forProgrammingLanguage(programmingLanguage))); for (var auxRepo : auxiliaryRepositories) { - repositoryMap.put(auxRepo.name(), new AeolusRepository(auxRepo.repositoryUrl().toString(), branch, auxRepo.name())); + repositoryMap.put(auxRepo.name(), new AeolusRepository(auxRepo.repositoryUri().toString(), branch, auxRepo.name())); } return repositoryMap; } diff --git a/src/main/java/de/tum/in/www1/artemis/service/connectors/bamboo/BambooBuildPlanService.java b/src/main/java/de/tum/in/www1/artemis/service/connectors/bamboo/BambooBuildPlanService.java index 3e3f7ecafe14..3ed269609b04 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/connectors/bamboo/BambooBuildPlanService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/connectors/bamboo/BambooBuildPlanService.java @@ -51,7 +51,7 @@ import de.tum.in.www1.artemis.domain.enumeration.*; import de.tum.in.www1.artemis.exception.ContinuousIntegrationBuildPlanException; import de.tum.in.www1.artemis.service.ResourceLoaderService; -import de.tum.in.www1.artemis.service.UrlService; +import de.tum.in.www1.artemis.service.UriService; import de.tum.in.www1.artemis.service.connectors.aeolus.AeolusBuildPlanService; import de.tum.in.www1.artemis.service.connectors.aeolus.AeolusRepository; import de.tum.in.www1.artemis.service.connectors.aeolus.Windfile; @@ -88,16 +88,16 @@ public class BambooBuildPlanService { private final Optional aeolusBuildPlanService; - private final UrlService urlService; + private final UriService uriService; public BambooBuildPlanService(ResourceLoaderService resourceLoaderService, BambooServer bambooServer, Optional versionControlService, - ProgrammingLanguageConfiguration programmingLanguageConfiguration, UrlService urlService, BambooInternalUrlService bambooInternalUrlService, + ProgrammingLanguageConfiguration programmingLanguageConfiguration, UriService uriService, BambooInternalUrlService bambooInternalUrlService, Optional aeolusBuildPlanService) { this.resourceLoaderService = resourceLoaderService; this.bambooServer = bambooServer; this.versionControlService = versionControlService; this.programmingLanguageConfiguration = programmingLanguageConfiguration; - this.urlService = urlService; + this.uriService = uriService; this.bambooInternalUrlService = bambooInternalUrlService; this.aeolusBuildPlanService = aeolusBuildPlanService; } @@ -108,14 +108,14 @@ public BambooBuildPlanService(ResourceLoaderService resourceLoaderService, Bambo * @param programmingExercise programming exercise with the required * information to create the base build plan * @param planKey the key of the build plan - * @param repositoryUrl the url of the assignment repository - * @param testRepositoryUrl the url of the test repository - * @param solutionRepositoryUrl the url of the solution repository + * @param repositoryUri the uri of the assignment repository + * @param testRepositoryUri the uri of the test repository + * @param solutionRepositoryUri the uri of the solution repository * @param auxiliaryRepositories List of auxiliary repositories to be included in * the build plan */ - public void createBuildPlanForExercise(ProgrammingExercise programmingExercise, String planKey, VcsRepositoryUrl repositoryUrl, VcsRepositoryUrl testRepositoryUrl, - VcsRepositoryUrl solutionRepositoryUrl, List auxiliaryRepositories) { + public void createBuildPlanForExercise(ProgrammingExercise programmingExercise, String planKey, VcsRepositoryUri repositoryUri, VcsRepositoryUri testRepositoryUri, + VcsRepositoryUri solutionRepositoryUri, List auxiliaryRepositories) { final String planDescription = planKey + " Build Plan for Exercise " + programmingExercise.getTitle(); final String projectKey = programmingExercise.getProjectKey(); final String projectName = programmingExercise.getProjectName(); @@ -124,13 +124,13 @@ public void createBuildPlanForExercise(ProgrammingExercise programmingExercise, String assignedKey = null; if (aeolusBuildPlanService.isPresent()) { - assignedKey = createCustomAeolusBuildPlanForExercise(programmingExercise, projectKey + "-" + planKey, planDescription, repositoryUrl, testRepositoryUrl, - solutionRepositoryUrl, auxiliaryRepositories); + assignedKey = createCustomAeolusBuildPlanForExercise(programmingExercise, projectKey + "-" + planKey, planDescription, repositoryUri, testRepositoryUri, + solutionRepositoryUri, auxiliaryRepositories); } if (assignedKey == null) { - Plan plan = createDefaultBuildPlan(planKey, planDescription, projectKey, projectName, repositoryUrl, testRepositoryUrl, - programmingExercise.getCheckoutSolutionRepository(), solutionRepositoryUrl, auxiliaryRepositories) + Plan plan = createDefaultBuildPlan(planKey, planDescription, projectKey, projectName, repositoryUri, testRepositoryUri, + programmingExercise.getCheckoutSolutionRepository(), solutionRepositoryUri, auxiliaryRepositories) .stages(createBuildStage(programmingExercise.getProgrammingLanguage(), programmingExercise.getProjectType(), programmingExercise.getPackageName(), programmingExercise.hasSequentialTestRuns(), programmingExercise.isStaticCodeAnalysisEnabled(), programmingExercise.getCheckoutSolutionRepository(), recordTestwiseCoverage, programmingExercise.getAuxiliaryRepositoriesForBuildPlan())); @@ -191,10 +191,10 @@ private Stage createBuildStage(ProgrammingLanguage programmingLanguage, ProjectT // exist in Artemis yet) boolean isMavenProject = ProjectType.isMavenProject(projectType); - var defaultTasks = new ArrayList>(); + List> defaultTasks = new ArrayList<>(); defaultTasks.add(checkoutTask); - var finalTasks = new ArrayList>(); - var artifacts = new ArrayList(); + List> finalTasks = new ArrayList<>(); + List artifacts = new ArrayList<>(); if (Boolean.TRUE.equals(staticCodeAnalysisEnabled)) { modifyBuildConfigurationForStaticCodeAnalysisForJavaAndKotlinExercise(isMavenProject, finalTasks, artifacts); @@ -207,16 +207,9 @@ private Stage createBuildStage(ProgrammingLanguage programmingLanguage, ProjectT modifyBuildConfigurationForSequentialTestsForJavaAndKotlinExercise(isMavenProject, defaultTasks, finalTasks); } - // This conversion is required because the attributes are passed as varargs-parameter which is only possible - // for array collections - var defaultTasksArray = defaultTasks.toArray(new Task[0]); - var finalTasksArray = finalTasks.toArray(new Task[0]); - var artifactsArray = artifacts.toArray(new Artifact[0]); - - // assign tasks and artifacts to job - defaultJob.tasks(defaultTasksArray); - defaultJob.finalTasks(finalTasksArray); - defaultJob.artifacts(artifactsArray); + defaultJob.tasks(defaultTasks.toArray(Task[]::new)); + defaultJob.finalTasks(finalTasks.toArray(Task[]::new)); + defaultJob.artifacts(artifacts.toArray(Artifact[]::new)); return defaultStage.jobs(defaultJob); } @@ -228,7 +221,7 @@ private Stage createBuildStage(ProgrammingLanguage programmingLanguage, ProjectT final Optional projectTypeSubdirectory = Optional.of(Path.of(projectType.name().toLowerCase())); final var tasks = readScriptTasksFromTemplate(programmingLanguage, projectTypeSubdirectory, null, sequentialBuildRuns, false); tasks.add(0, checkoutTask); - defaultJob.tasks(tasks.toArray(new Task[0])); + defaultJob.tasks(tasks.toArray(Task[]::new)); // Final tasks: final TestParserTask testParserTask = new TestParserTask(TestParserTaskProperties.TestType.JUNIT).resultDirectories("test-reports/*results.xml"); @@ -242,7 +235,7 @@ private Stage createBuildStage(ProgrammingLanguage programmingLanguage, ProjectT .toArray(Artifact[]::new); defaultJob.artifacts(artifacts); final var scaTasks = readScriptTasksFromTemplate(programmingLanguage, Optional.empty(), null, false, true); - defaultJob.finalTasks(scaTasks.toArray(new Task[0])); + defaultJob.finalTasks(scaTasks.toArray(Task[]::new)); } // Do not remove target, so the report can be sent to Artemis @@ -272,7 +265,7 @@ private Stage createBuildStage(ProgrammingLanguage programmingLanguage, ProjectT } final var tasks = readScriptTasksFromTemplate(programmingLanguage, subDirectory, replacements, sequentialBuildRuns, false); tasks.add(0, checkoutTask); - defaultJob.tasks(tasks.toArray(new Task[0])).finalTasks(testParserTask); + defaultJob.tasks(tasks.toArray(Task[]::new)).finalTasks(testParserTask); if (Boolean.TRUE.equals(staticCodeAnalysisEnabled)) { // Create artifacts and a final task for the execution of static code analysis final List staticCodeAnalysisTools = StaticCodeAnalysisTool.getToolsForProgrammingLanguage(ProgrammingLanguage.SWIFT); @@ -281,7 +274,7 @@ private Stage createBuildStage(ProgrammingLanguage programmingLanguage, ProjectT .toArray(Artifact[]::new); defaultJob.artifacts(artifacts); final var scaTasks = readScriptTasksFromTemplate(programmingLanguage, subDirectory, null, false, true); - defaultJob.finalTasks(scaTasks.toArray(new Task[0])); + defaultJob.finalTasks(scaTasks.toArray(Task[]::new)); } if (isXcodeProject) { // add a requirement to be able to run the Xcode build tasks using fastlane @@ -396,28 +389,27 @@ private Stage createDefaultStage(ProgrammingLanguage programmingLanguage, boolea final var testParserTask = new TestParserTask(TestParserTaskProperties.TestType.JUNIT).resultDirectories(resultDirectories); final var tasks = readScriptTasksFromTemplate(programmingLanguage, Optional.empty(), null, sequentialBuildRuns, false); tasks.add(0, checkoutTask); - return defaultStage.jobs(defaultJob.tasks(tasks.toArray(new Task[0])).finalTasks(testParserTask)); + return defaultStage.jobs(defaultJob.tasks(tasks.toArray(Task[]::new)).finalTasks(testParserTask)); } - // TODO: aux repos also need to have a URL and not a slug - private Plan createDefaultBuildPlan(String planKey, String planDescription, String projectKey, String projectName, VcsRepositoryUrl assignmentRepoUrl, - VcsRepositoryUrl testRepoUrl, boolean checkoutSolutionRepository, VcsRepositoryUrl solutionRepoUrl, - List auxiliaryRepositories) { + private Plan createDefaultBuildPlan(String planKey, String planDescription, String projectKey, String projectName, VcsRepositoryUri assignmentRepoUri, + VcsRepositoryUri testRepoUri, boolean checkoutSolutionRepository, VcsRepositoryUri solutionRepoUri, + List auxiliaryRepositories) { VersionControlService versionControl = versionControlService.orElseThrow(); - List> planRepositories = new ArrayList<>(); - planRepositories.add(createBuildPlanRepository(ASSIGNMENT_REPO_NAME, bambooInternalUrlService.toInternalVcsUrl(assignmentRepoUrl).toString(), - versionControl.getDefaultBranchOfRepository(projectKey, urlService.getRepositorySlugFromRepositoryUrl(assignmentRepoUrl)))); - planRepositories.add(createBuildPlanRepository(TEST_REPO_NAME, bambooInternalUrlService.toInternalVcsUrl(testRepoUrl).toString(), - versionControl.getDefaultBranchOfRepository(projectKey, urlService.getRepositorySlugFromRepositoryUrl(testRepoUrl)))); + List planRepositories = new ArrayList<>(); + planRepositories.add(createBuildPlanGitRepository(ASSIGNMENT_REPO_NAME, bambooInternalUrlService.toInternalVcsUrl(assignmentRepoUri).toString(), + versionControl.getDefaultBranchOfRepository(projectKey, uriService.getRepositorySlugFromRepositoryUri(assignmentRepoUri)))); + planRepositories.add(createBuildPlanGitRepository(TEST_REPO_NAME, bambooInternalUrlService.toInternalVcsUrl(testRepoUri).toString(), + versionControl.getDefaultBranchOfRepository(projectKey, uriService.getRepositorySlugFromRepositoryUri(testRepoUri)))); for (var auxRepo : auxiliaryRepositories) { - planRepositories.add(createBuildPlanRepository(auxRepo.name(), bambooInternalUrlService.toInternalVcsUrl(auxRepo.repositoryUrl()).toString(), - versionControl.getDefaultBranchOfRepository(projectKey, urlService.getRepositorySlugFromRepositoryUrl(auxRepo.repositoryUrl())))); + planRepositories.add(createBuildPlanGitRepository(auxRepo.name(), bambooInternalUrlService.toInternalVcsUrl(auxRepo.repositoryUri()).toString(), + versionControl.getDefaultBranchOfRepository(projectKey, uriService.getRepositorySlugFromRepositoryUri(auxRepo.repositoryUri())))); } if (checkoutSolutionRepository) { - planRepositories.add(createBuildPlanRepository(SOLUTION_REPO_NAME, bambooInternalUrlService.toInternalVcsUrl(solutionRepoUrl).toString(), - versionControl.getDefaultBranchOfRepository(projectKey, urlService.getRepositorySlugFromRepositoryUrl(solutionRepoUrl)))); + planRepositories.add(createBuildPlanGitRepository(SOLUTION_REPO_NAME, bambooInternalUrlService.toInternalVcsUrl(solutionRepoUri).toString(), + versionControl.getDefaultBranchOfRepository(projectKey, uriService.getRepositorySlugFromRepositoryUri(solutionRepoUri)))); } return new Plan(createBuildProject(projectName, projectKey), planKey, planKey).description(planDescription) @@ -450,11 +442,9 @@ private Notification createNotification() { .recipientString(artemisServerUrl + NEW_RESULT_RESOURCE_API_PATH)); } - private GitRepository createBuildPlanRepository(String name, String repositoryUrl, String branch) { + private GitRepository createBuildPlanGitRepository(String name, String repositoryUri, String branch) { return new GitRepository().name(name).branch(branch).authentication(new SharedCredentialsIdentifier(gitUser).scope(SharedCredentialsScope.GLOBAL)) - .url(repositoryUrl.toLowerCase()).shallowClonesEnabled(true).remoteAgentCacheEnabled(false) - // TODO: can we leave this empty? - .changeDetection(new VcsChangeDetection()); + .url(repositoryUri.toLowerCase()).shallowClonesEnabled(true).remoteAgentCacheEnabled(false).changeDetection(new VcsChangeDetection()); } private PlanPermissions generatePlanPermissions(String bambooProjectKey, String bambooPlanKey, @Nullable String teachingAssistantGroupName, @Nullable String editorGroupName, @@ -543,8 +533,8 @@ else if (sequentialBuildRuns) { /** * Assembles a bamboo docker configuration for a given programming exercise and project type * - * @param programmingLanguage - * @param projectType + * @param programmingLanguage the chosen language of the programming exercise + * @param projectType the chosen project type of the programming exercise * @return bamboo docker configuration */ private DockerConfiguration dockerConfigurationFor(ProgrammingLanguage programmingLanguage, Optional projectType) { @@ -563,7 +553,7 @@ private DockerConfiguration dockerConfigurationFor(ProgrammingLanguage programmi * @return An array of string containing all the configured docker run argument key-value pairs prefixed with two dashes */ private String[] getDefaultDockerRunArguments() { - return programmingLanguageConfiguration.getDefaultDockerFlags().toArray(new String[0]); + return programmingLanguageConfiguration.getDefaultDockerFlags().toArray(String[]::new); } /** @@ -575,14 +565,14 @@ private String[] getDefaultDockerRunArguments() { * @param programmingExercise the programming exercise for which to create the build plan * @param buildPlanId the id of the build plan * @param planDescription the description of the build plan - * @param repositoryUrl the url of the assignment repository - * @param testRepositoryUrl the url of the test repository - * @param solutionRepositoryUrl the url of the solution repository + * @param repositoryUri the url of the assignment repository + * @param testRepositoryUri the url of the test repository + * @param solutionRepositoryUri the url of the solution repository * @param auxiliaryRepositories List of auxiliary repositories to be included in the build plan * @return the key of the created build plan, or null if it could not be created */ - private String createCustomAeolusBuildPlanForExercise(ProgrammingExercise programmingExercise, String buildPlanId, String planDescription, VcsRepositoryUrl repositoryUrl, - VcsRepositoryUrl testRepositoryUrl, VcsRepositoryUrl solutionRepositoryUrl, List auxiliaryRepositories) + private String createCustomAeolusBuildPlanForExercise(ProgrammingExercise programmingExercise, String buildPlanId, String planDescription, VcsRepositoryUri repositoryUri, + VcsRepositoryUri testRepositoryUri, VcsRepositoryUri solutionRepositoryUri, List auxiliaryRepositories) throws ContinuousIntegrationBuildPlanException { if (aeolusBuildPlanService.isEmpty()) { return null; @@ -595,7 +585,7 @@ private String createCustomAeolusBuildPlanForExercise(ProgrammingExercise progra try { Windfile windfile = programmingExercise.getWindfile(); Map repositories = aeolusBuildPlanService.get().createRepositoryMapForWindfile(programmingExercise.getProgrammingLanguage(), - programmingExercise.getBranch(), programmingExercise.getCheckoutSolutionRepository(), repositoryUrl, testRepositoryUrl, solutionRepositoryUrl, + programmingExercise.getBranch(), programmingExercise.getCheckoutSolutionRepository(), repositoryUri, testRepositoryUri, solutionRepositoryUri, auxiliaryRepositories); String resultHookUrl = artemisServerUrl + NEW_RESULT_RESOURCE_API_PATH; diff --git a/src/main/java/de/tum/in/www1/artemis/service/connectors/bamboo/BambooService.java b/src/main/java/de/tum/in/www1/artemis/service/connectors/bamboo/BambooService.java index af84eef88db9..e9ebba150126 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/connectors/bamboo/BambooService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/connectors/bamboo/BambooService.java @@ -43,7 +43,7 @@ import de.tum.in.www1.artemis.repository.FeedbackRepository; import de.tum.in.www1.artemis.repository.ProgrammingSubmissionRepository; import de.tum.in.www1.artemis.service.BuildLogEntryService; -import de.tum.in.www1.artemis.service.UrlService; +import de.tum.in.www1.artemis.service.UriService; import de.tum.in.www1.artemis.service.connectors.*; import de.tum.in.www1.artemis.service.connectors.bamboo.dto.*; import de.tum.in.www1.artemis.service.connectors.ci.AbstractContinuousIntegrationService; @@ -66,7 +66,7 @@ public class BambooService extends AbstractContinuousIntegrationService { private final ObjectMapper mapper; - private final UrlService urlService; + private final UriService uriService; private final RestTemplate restTemplate; @@ -74,43 +74,43 @@ public class BambooService extends AbstractContinuousIntegrationService { public BambooService(ProgrammingSubmissionRepository programmingSubmissionRepository, Optional continuousIntegrationUpdateService, BambooBuildPlanService bambooBuildPlanService, FeedbackRepository feedbackRepository, @Qualifier("bambooRestTemplate") RestTemplate restTemplate, - @Qualifier("shortTimeoutBambooRestTemplate") RestTemplate shortTimeoutRestTemplate, ObjectMapper mapper, UrlService urlService, BuildLogEntryService buildLogService, + @Qualifier("shortTimeoutBambooRestTemplate") RestTemplate shortTimeoutRestTemplate, ObjectMapper mapper, UriService uriService, BuildLogEntryService buildLogService, TestwiseCoverageService testwiseCoverageService, BuildLogStatisticsEntryRepository buildLogStatisticsEntryRepository) { super(programmingSubmissionRepository, feedbackRepository, buildLogService, buildLogStatisticsEntryRepository, testwiseCoverageService); this.continuousIntegrationUpdateService = continuousIntegrationUpdateService; this.bambooBuildPlanService = bambooBuildPlanService; this.mapper = mapper; - this.urlService = urlService; + this.uriService = uriService; this.restTemplate = restTemplate; this.shortTimeoutRestTemplate = shortTimeoutRestTemplate; } @Override - public void createBuildPlanForExercise(ProgrammingExercise programmingExercise, String planKey, VcsRepositoryUrl sourceCodeRepositoryURL, VcsRepositoryUrl testRepositoryURL, - VcsRepositoryUrl solutionRepositoryURL) { + public void createBuildPlanForExercise(ProgrammingExercise programmingExercise, String planKey, VcsRepositoryUri repositoryUri, VcsRepositoryUri testRepositoryUri, + VcsRepositoryUri solutionRepositoryUri) { var additionalRepositories = programmingExercise.getAuxiliaryRepositoriesForBuildPlan().stream() - .map(repo -> new AuxiliaryRepository.AuxRepoNameWithUrl(repo.getName(), repo.getVcsRepositoryUrl())).toList(); - bambooBuildPlanService.createBuildPlanForExercise(programmingExercise, planKey, sourceCodeRepositoryURL, testRepositoryURL, solutionRepositoryURL, additionalRepositories); + .map(repo -> new AuxiliaryRepository.AuxRepoNameWithUri(repo.getName(), repo.getVcsRepositoryUri())).toList(); + bambooBuildPlanService.createBuildPlanForExercise(programmingExercise, planKey, repositoryUri, testRepositoryUri, solutionRepositoryUri, additionalRepositories); } @Override public void recreateBuildPlansForExercise(ProgrammingExercise exercise) { deleteBuildPlan(exercise.getProjectKey(), exercise.getTemplateBuildPlanId()); deleteBuildPlan(exercise.getProjectKey(), exercise.getSolutionBuildPlanId()); - createBuildPlanForExercise(exercise, BuildPlanType.TEMPLATE.getName(), exercise.getVcsTemplateRepositoryUrl(), exercise.getVcsTestRepositoryUrl(), - exercise.getVcsSolutionRepositoryUrl()); - createBuildPlanForExercise(exercise, BuildPlanType.SOLUTION.getName(), exercise.getVcsSolutionRepositoryUrl(), exercise.getVcsTestRepositoryUrl(), - exercise.getVcsSolutionRepositoryUrl()); + createBuildPlanForExercise(exercise, BuildPlanType.TEMPLATE.getName(), exercise.getVcsTemplateRepositoryUri(), exercise.getVcsTestRepositoryUri(), + exercise.getVcsSolutionRepositoryUri()); + createBuildPlanForExercise(exercise, BuildPlanType.SOLUTION.getName(), exercise.getVcsSolutionRepositoryUri(), exercise.getVcsTestRepositoryUri(), + exercise.getVcsSolutionRepositoryUri()); } @Override public void configureBuildPlan(ProgrammingExerciseParticipation participation, String branch) { String buildPlanId = participation.getBuildPlanId(); - VcsRepositoryUrl repositoryUrl = participation.getVcsRepositoryUrl(); + VcsRepositoryUri repositoryUri = participation.getVcsRepositoryUri(); String projectKey = getProjectKeyFromBuildPlanId(buildPlanId); - String repoProjectName = urlService.getProjectKeyFromRepositoryUrl(repositoryUrl); - String plainRepositoryUrl = urlService.getPlainUrlFromRepositoryUrl(repositoryUrl); - updatePlanRepository(projectKey, buildPlanId, ASSIGNMENT_REPO_NAME, repoProjectName, plainRepositoryUrl, null /* not needed */, branch); + String repoProjectName = uriService.getProjectKeyFromRepositoryUri(repositoryUri); + String plainRepositoryUri = uriService.getPlainUriFromRepositoryUri(repositoryUri); + updatePlanRepository(projectKey, buildPlanId, ASSIGNMENT_REPO_NAME, repoProjectName, plainRepositoryUri, null /* not needed */, branch); enablePlan(projectKey, buildPlanId); // allow student or team access to the build plan in case this option was specified (only available for course exercises) @@ -439,9 +439,9 @@ public void enablePlan(String projectKey, String planKey) throws BambooException } @Override - public void updatePlanRepository(String buildProjectKey, String buildPlanKey, String ciRepoName, String repoProjectKey, String newRepoUrl, String existingRepoUrl, + public void updatePlanRepository(String buildProjectKey, String buildPlanKey, String ciRepoName, String repoProjectKey, String newRepoUri, String existingRepoUri, String newBranch) throws BambooException { - continuousIntegrationUpdateService.orElseThrow().updatePlanRepository(buildPlanKey, ciRepoName, newRepoUrl, newBranch); + continuousIntegrationUpdateService.orElseThrow().updatePlanRepository(buildPlanKey, ciRepoName, newRepoUri, newBranch); } /** diff --git a/src/main/java/de/tum/in/www1/artemis/service/connectors/bitbucket/BambooBuildPlanUpdateService.java b/src/main/java/de/tum/in/www1/artemis/service/connectors/bitbucket/BambooBuildPlanUpdateService.java index 38a6825d74ad..e4d8ee4d5eb1 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/connectors/bitbucket/BambooBuildPlanUpdateService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/connectors/bitbucket/BambooBuildPlanUpdateService.java @@ -49,7 +49,7 @@ public BambooBuildPlanUpdateService(@Qualifier("bambooRestTemplate") RestTemplat } @Override - public void updatePlanRepository(String buildPlanKey, String bambooRepositoryName, String newRepoUrl, String branchName) { + public void updatePlanRepository(String buildPlanKey, String bambooRepositoryName, String newRepoUri, String branchName) { try { log.debug("Update plan repository for build plan {}", buildPlanKey); BambooRepositoryDTO bambooRepository = findBambooRepository(bambooRepositoryName, OLD_ASSIGNMENT_REPO_NAME, buildPlanKey); @@ -58,7 +58,7 @@ public void updatePlanRepository(String buildPlanKey, String bambooRepositoryNam + " to the student repository : Could not find assignment nor Assignment repository"); } - updateBambooPlanRepository(bambooRepository, buildPlanKey, branchName, bambooInternalUrlService.toInternalVcsUrl(newRepoUrl)); + updateBambooPlanRepository(bambooRepository, buildPlanKey, branchName, bambooInternalUrlService.toInternalVcsUrl(newRepoUri)); log.info("Update plan repository for build plan {} was successful", buildPlanKey); } @@ -75,7 +75,7 @@ public void updatePlanRepository(String buildPlanKey, String bambooRepositoryNam * @param bambooRepository the bamboo repository which was obtained before * @param buildPlanKey the complete name of the plan */ - private void updateBambooPlanRepository(@NotNull BambooRepositoryDTO bambooRepository, String buildPlanKey, String branchName, String newRepoUrl) { + private void updateBambooPlanRepository(@NotNull BambooRepositoryDTO bambooRepository, String buildPlanKey, String branchName, String newRepoUri) { MultiValueMap parameters = new LinkedMultiValueMap<>(); parameters.add("planKey", buildPlanKey); parameters.add("selectedRepository", "com.atlassian.bamboo.plugins.atlassian-bamboo-plugin-git:gitv2"); @@ -86,7 +86,7 @@ private void updateBambooPlanRepository(@NotNull BambooRepositoryDTO bambooRepos parameters.add("save", "Save repository"); parameters.add("bamboo.successReturnMode", "json"); parameters.add("repository.git.branch", branchName); - parameters.add("repository.git.repositoryUrl", newRepoUrl); + parameters.add("repository.git.repositoryUri", newRepoUri); parameters.add("repository.git.authenticationType", "PASSWORD"); parameters.add("repository.git.passwordCredentialsSource", "SHARED_CREDENTIALS"); parameters.add("___advancedOptionsPresent___", "true"); diff --git a/src/main/java/de/tum/in/www1/artemis/service/connectors/bitbucket/BitbucketService.java b/src/main/java/de/tum/in/www1/artemis/service/connectors/bitbucket/BitbucketService.java index 2071fd593c6a..efbe4e18080d 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/connectors/bitbucket/BitbucketService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/connectors/bitbucket/BitbucketService.java @@ -37,7 +37,7 @@ import de.tum.in.www1.artemis.exception.BitbucketException; import de.tum.in.www1.artemis.exception.VersionControlException; import de.tum.in.www1.artemis.repository.*; -import de.tum.in.www1.artemis.service.UrlService; +import de.tum.in.www1.artemis.service.UriService; import de.tum.in.www1.artemis.service.connectors.ConnectorHealth; import de.tum.in.www1.artemis.service.connectors.GitService; import de.tum.in.www1.artemis.service.connectors.bitbucket.dto.*; @@ -67,11 +67,11 @@ public class BitbucketService extends AbstractVersionControlService { private final RestTemplate shortTimeoutRestTemplate; - public BitbucketService(@Qualifier("bitbucketRestTemplate") RestTemplate restTemplate, UserRepository userRepository, UrlService urlService, + public BitbucketService(@Qualifier("bitbucketRestTemplate") RestTemplate restTemplate, UserRepository userRepository, UriService uriService, @Qualifier("shortTimeoutBitbucketRestTemplate") RestTemplate shortTimeoutRestTemplate, GitService gitService, ApplicationContext applicationContext, ProgrammingExerciseStudentParticipationRepository studentParticipationRepository, ProgrammingExerciseRepository programmingExerciseRepository, TemplateProgrammingExerciseParticipationRepository templateProgrammingExerciseParticipationRepository) { - super(applicationContext, gitService, urlService, studentParticipationRepository, programmingExerciseRepository, templateProgrammingExerciseParticipationRepository); + super(gitService, uriService, studentParticipationRepository, programmingExerciseRepository, templateProgrammingExerciseParticipationRepository); this.userRepository = userRepository; this.restTemplate = restTemplate; this.shortTimeoutRestTemplate = shortTimeoutRestTemplate; @@ -89,26 +89,26 @@ public void configureRepository(ProgrammingExercise exercise, ProgrammingExercis if (allowAccess) { final VersionControlRepositoryPermission permissions = determineRepositoryPermissions(exercise); - addMemberToRepository(participation.getVcsRepositoryUrl(), user, permissions); + addMemberToRepository(participation.getVcsRepositoryUri(), user, permissions); } } // TODO: we should separate access (above) from protecting branches - protectBranches(urlService.getProjectKeyFromRepositoryUrl(participation.getVcsRepositoryUrl()), - urlService.getRepositorySlugFromRepositoryUrl(participation.getVcsRepositoryUrl())); + protectBranches(uriService.getProjectKeyFromRepositoryUri(participation.getVcsRepositoryUri()), + uriService.getRepositorySlugFromRepositoryUri(participation.getVcsRepositoryUri())); } @Override - public void addMemberToRepository(VcsRepositoryUrl repositoryUrl, User user, VersionControlRepositoryPermission permissions) { - final String projectKey = urlService.getProjectKeyFromRepositoryUrl(repositoryUrl); - final String urlSlug = urlService.getRepositorySlugFromRepositoryUrl(repositoryUrl); + public void addMemberToRepository(VcsRepositoryUri repositoryUri, User user, VersionControlRepositoryPermission permissions) { + final String projectKey = uriService.getProjectKeyFromRepositoryUri(repositoryUri); + final String urlSlug = uriService.getRepositorySlugFromRepositoryUri(repositoryUri); giveRepoPermission(projectKey, urlSlug, user.getLogin(), permissions); } @Override - public void removeMemberFromRepository(VcsRepositoryUrl repositoryUrl, User user) { - removeStudentRepositoryAccess(repositoryUrl, urlService.getProjectKeyFromRepositoryUrl(repositoryUrl), user.getLogin()); + public void removeMemberFromRepository(VcsRepositoryUri repositoryUri, User user) { + removeStudentRepositoryAccess(repositoryUri, uriService.getProjectKeyFromRepositoryUri(repositoryUri), user.getLogin()); } /** @@ -146,14 +146,14 @@ private void protectBranches(String projectKey, String repositorySlug) { } @Override - protected void addWebHook(VcsRepositoryUrl repositoryUrl, String notificationUrl, String webHookName) { - if (!webHookExists(urlService.getProjectKeyFromRepositoryUrl(repositoryUrl), urlService.getRepositorySlugFromRepositoryUrl(repositoryUrl))) { - createWebHook(urlService.getProjectKeyFromRepositoryUrl(repositoryUrl), urlService.getRepositorySlugFromRepositoryUrl(repositoryUrl), notificationUrl, webHookName); + protected void addWebHook(VcsRepositoryUri repositoryUri, String notificationUrl, String webHookName) { + if (!webHookExists(uriService.getProjectKeyFromRepositoryUri(repositoryUri), uriService.getRepositorySlugFromRepositoryUri(repositoryUri))) { + createWebHook(uriService.getProjectKeyFromRepositoryUri(repositoryUri), uriService.getRepositorySlugFromRepositoryUri(repositoryUri), notificationUrl, webHookName); } } @Override - protected void addAuthenticatedWebHook(VcsRepositoryUrl repositoryUrl, String notificationUrl, String webHookName, String secretToken) { + protected void addAuthenticatedWebHook(VcsRepositoryUri repositoryUri, String notificationUrl, String webHookName, String secretToken) { // Not needed for Bitbucket throw new UnsupportedOperationException("Authenticated webhooks with Bitbucket are not supported!"); } @@ -172,9 +172,9 @@ public void deleteProject(String projectKey) { } @Override - public void deleteRepository(VcsRepositoryUrl repositoryUrl) { - final String projectKey = urlService.getProjectKeyFromRepositoryUrl(repositoryUrl); - final String repositorySlug = urlService.getRepositorySlugFromRepositoryUrl(repositoryUrl); + public void deleteRepository(VcsRepositoryUri repositoryUri) { + final String projectKey = uriService.getProjectKeyFromRepositoryUri(repositoryUri); + final String repositorySlug = uriService.getRepositorySlugFromRepositoryUri(repositoryUri); final String baseUrl = bitbucketServerUrl + "/rest/api/latest/projects/" + projectKey + "/repos/" + repositorySlug.toLowerCase(); log.info("Delete repository {}", baseUrl); try { @@ -186,8 +186,8 @@ public void deleteRepository(VcsRepositoryUrl repositoryUrl) { } @Override - public VcsRepositoryUrl getCloneRepositoryUrl(String projectKey, String repositorySlug) { - return new BitbucketRepositoryUrl(projectKey, repositorySlug); + public VcsRepositoryUri getCloneRepositoryUri(String projectKey, String repositorySlug) { + return new BitbucketRepositoryUri(projectKey, repositorySlug); } private BitbucketProjectDTO getBitbucketProject(String projectKey) { @@ -456,20 +456,20 @@ private void giveRepoPermission(String projectKey, String repositorySlug, String } @Override - public void setRepositoryPermissionsToReadOnly(VcsRepositoryUrl repositoryUrl, String projectKey, Set users) throws BitbucketException { - users.forEach(user -> setStudentRepositoryPermission(repositoryUrl, projectKey, user.getLogin(), VersionControlRepositoryPermission.REPO_READ)); + public void setRepositoryPermissionsToReadOnly(VcsRepositoryUri repositoryUri, String projectKey, Set users) throws BitbucketException { + users.forEach(user -> setStudentRepositoryPermission(repositoryUri, projectKey, user.getLogin(), VersionControlRepositoryPermission.REPO_READ)); } /** * Get the default branch of the repository * - * @param repositoryUrl The repository url to get the default branch for. + * @param repositoryUri The repository uri to get the default branch for. * @return the name of the default branch, e.g. 'main' */ @Override - public String getDefaultBranchOfRepository(VcsRepositoryUrl repositoryUrl) throws BitbucketException { - String projectKey = urlService.getProjectKeyFromRepositoryUrl(repositoryUrl); - String repositorySlug = urlService.getRepositorySlugFromRepositoryUrl(repositoryUrl); + public String getDefaultBranchOfRepository(VcsRepositoryUri repositoryUri) throws BitbucketException { + String projectKey = uriService.getProjectKeyFromRepositoryUri(repositoryUri); + String repositorySlug = uriService.getRepositorySlugFromRepositoryUri(repositoryUri); var getDefaultBranchUrl = bitbucketServerUrl + "/rest/api/latest/projects/" + projectKey + "/repos/" + repositorySlug.toLowerCase() + "/default-branch"; try { @@ -490,21 +490,21 @@ public String getDefaultBranchOfRepository(VcsRepositoryUrl repositoryUrl) throw } @Override - public void unprotectBranch(VcsRepositoryUrl repositoryUrl, String branch) throws VersionControlException { + public void unprotectBranch(VcsRepositoryUri repositoryUri, String branch) throws VersionControlException { // Not implemented because it's not needed in Bitbucket for the current use case, because the main branch is not protected by default } /** * Set the permission of a student for a repository * - * @param repositoryUrl The complete repository-url (including protocol, host and the complete path) + * @param repositoryUri The complete repository-url (including protocol, host and the complete path) * @param projectKey The project key of the repository's project. * @param username The username of the user whom to assign a permission level * @param repositoryPermission Repository permission to set for the user (e.g. READ_ONLY, WRITE) */ - private void setStudentRepositoryPermission(VcsRepositoryUrl repositoryUrl, String projectKey, String username, VersionControlRepositoryPermission repositoryPermission) + private void setStudentRepositoryPermission(VcsRepositoryUri repositoryUri, String projectKey, String username, VersionControlRepositoryPermission repositoryPermission) throws BitbucketException { - String repositorySlug = urlService.getRepositorySlugFromRepositoryUrl(repositoryUrl); + String repositorySlug = uriService.getRepositorySlugFromRepositoryUri(repositoryUri); String baseUrl = bitbucketServerUrl + "/rest/api/latest/projects/" + projectKey + "/repos/" + repositorySlug + "/permissions/users?name="; // NAME&PERMISSION String url = baseUrl + username + "&permission=" + repositoryPermission; try { @@ -519,12 +519,12 @@ private void setStudentRepositoryPermission(VcsRepositoryUrl repositoryUrl, Stri /** * Remove all permissions of a student for a repository * - * @param repositoryUrl The complete repository-url (including protocol, host and the complete path) + * @param repositoryUri The complete repository-url (including protocol, host and the complete path) * @param projectKey The project key of the repository's project. * @param username The username of the user whom to remove access */ - private void removeStudentRepositoryAccess(VcsRepositoryUrl repositoryUrl, String projectKey, String username) throws BitbucketException { - String repositorySlug = urlService.getRepositorySlugFromRepositoryUrl(repositoryUrl); + private void removeStudentRepositoryAccess(VcsRepositoryUri repositoryUri, String projectKey, String username) throws BitbucketException { + String repositorySlug = uriService.getRepositorySlugFromRepositoryUri(repositoryUri); String baseUrl = bitbucketServerUrl + "/rest/api/latest/projects/" + projectKey + "/repos/" + repositorySlug + "/permissions/users?name="; // NAME String url = baseUrl + username; try { @@ -728,15 +728,15 @@ private void createWebHook(String projectKey, String repositorySlug, String noti } @Override - public Boolean repositoryUrlIsValid(@Nullable VcsRepositoryUrl repositoryUrl) { - if (repositoryUrl == null || repositoryUrl.getURI() == null) { + public Boolean repositoryUriIsValid(@Nullable VcsRepositoryUri repositoryUri) { + if (repositoryUri == null || repositoryUri.getURI() == null) { return false; } String projectKey; String repositorySlug; try { - projectKey = urlService.getProjectKeyFromRepositoryUrl(repositoryUrl); - repositorySlug = urlService.getRepositorySlugFromRepositoryUrl(repositoryUrl); + projectKey = uriService.getProjectKeyFromRepositoryUri(repositoryUri); + repositorySlug = uriService.getRepositorySlugFromRepositoryUri(repositoryUri); } catch (BitbucketException e) { // Either the project Key or the repository slug could not be extracted, therefore this can't be a valid URL @@ -813,7 +813,7 @@ public ZonedDateTime getPushDate(ProgrammingExerciseParticipation participation, try { UriComponents builder = UriComponentsBuilder.fromUri(bitbucketServerUrl.toURI()) .pathSegment("rest", "api", "latest", "projects", participation.getProgrammingExercise().getProjectKey(), "repos", - urlService.getRepositorySlugFromRepositoryUrl(participation.getVcsRepositoryUrl()), "ref-change-activities") + uriService.getRepositorySlugFromRepositoryUri(participation.getVcsRepositoryUri()), "ref-change-activities") .queryParam("start", start).queryParam("limit", perPage).queryParam("ref", "refs/heads/" + defaultBranch).build(); final var response = restTemplate.exchange(builder.toUri(), HttpMethod.GET, null, BitbucketChangeActivitiesDTO.class); if (response.getStatusCode() != HttpStatus.OK || response.getBody() == null) { @@ -839,18 +839,18 @@ public ZonedDateTime getPushDate(ProgrammingExerciseParticipation participation, private JsonNode fetchCommitInfo(JsonNode commitData, String hash) { try { var cloneLinks = commitData.get("repository").get("links").get("clone"); - VcsRepositoryUrl repositoryURL; + VcsRepositoryUri repositoryUri; // it might be the case that cloneLinks contains two URLs and the first one is 'ssh'. Then we are interested in http // we use contains here, because it could be the case that https is used here as well in the future. // It should not be possible that the cloneLinks array is empty. if (cloneLinks.size() > 1 && !cloneLinks.get(0).get("name").asText().contains("http")) { - repositoryURL = new VcsRepositoryUrl(cloneLinks.get(1).get("href").asText()); + repositoryUri = new VcsRepositoryUri(cloneLinks.get(1).get("href").asText()); } else { - repositoryURL = new VcsRepositoryUrl(cloneLinks.get(0).get("href").asText()); + repositoryUri = new VcsRepositoryUri(cloneLinks.get(0).get("href").asText()); } - final var projectKey = urlService.getProjectKeyFromRepositoryUrl(repositoryURL); - final var slug = urlService.getRepositorySlugFromRepositoryUrl(repositoryURL); + final var projectKey = uriService.getProjectKeyFromRepositoryUri(repositoryUri); + final var slug = uriService.getRepositorySlugFromRepositoryUri(repositoryUri); final var uriBuilder = UriComponentsBuilder.fromUri(bitbucketServerUrl.toURI()) .pathSegment("rest", "api", "1.0", "projects", projectKey, "repos", slug, "commits", hash).build(); final var commitInfo = restTemplate.exchange(uriBuilder.toUri(), HttpMethod.GET, null, JsonNode.class).getBody(); @@ -871,31 +871,31 @@ public void createRepository(String entityName, String topLevelEntity, String pa createRepository(entityName, topLevelEntity); } - public final class BitbucketRepositoryUrl extends VcsRepositoryUrl { + public final class BitbucketRepositoryUri extends VcsRepositoryUri { - public BitbucketRepositoryUrl(String projectKey, String repositorySlug) { + public BitbucketRepositoryUri(String projectKey, String repositorySlug) { final var urlString = bitbucketServerUrl.getProtocol() + "://" + bitbucketServerUrl.getAuthority() + buildRepositoryPath(projectKey, repositorySlug); try { this.uri = new URI(urlString); } catch (URISyntaxException e) { - throw new BitbucketException("Could not Bitbucket Repository URL", e); + throw new BitbucketException("Could not Bitbucket Repository URI", e); } } - private BitbucketRepositoryUrl(String urlString) { + private BitbucketRepositoryUri(String urlString) { try { this.uri = new URI(urlString); } catch (URISyntaxException e) { - throw new BitbucketException("Could not Bitbucket Repository URL", e); + throw new BitbucketException("Could not Bitbucket Repository URI", e); } } @Override - public VcsRepositoryUrl withUser(String username) { + public VcsRepositoryUri withUser(String username) { this.username = username; - return new BitbucketRepositoryUrl(uri.toString().replaceAll("(https?://)(.*)", "$1" + username + "@$2")); + return new BitbucketRepositoryUri(uri.toString().replaceAll("(https?://)(.*)", "$1" + username + "@$2")); } private String buildRepositoryPath(String projectKey, String repositorySlug) { diff --git a/src/main/java/de/tum/in/www1/artemis/service/connectors/ci/ContinuousIntegrationService.java b/src/main/java/de/tum/in/www1/artemis/service/connectors/ci/ContinuousIntegrationService.java index 0b4f6ee86279..f08dc8ee9640 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/connectors/ci/ContinuousIntegrationService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/connectors/ci/ContinuousIntegrationService.java @@ -36,12 +36,12 @@ enum BuildStatus { * * @param exercise a programming exercise with the required information to create the base build plan * @param planKey the key of the plan - * @param repositoryURL the URL of the assignment repository (used to separate between exercise and solution) - * @param testRepositoryURL the URL of the test repository - * @param solutionRepositoryURL the URL of the solution repository. Only used for HASKELL exercises with checkoutSolutionRepository=true. Otherwise ignored. + * @param repositoryUri the URI of the assignment repository (used to separate between exercise and solution) + * @param testRepositoryUri the URI of the test repository + * @param solutionRepositoryUri the URI of the solution repository. Only used for HASKELL exercises with checkoutSolutionRepository=true. Otherwise ignored. */ - void createBuildPlanForExercise(ProgrammingExercise exercise, String planKey, VcsRepositoryUrl repositoryURL, VcsRepositoryUrl testRepositoryURL, - VcsRepositoryUrl solutionRepositoryURL); + void createBuildPlanForExercise(ProgrammingExercise exercise, String planKey, VcsRepositoryUri repositoryUri, VcsRepositoryUri testRepositoryUri, + VcsRepositoryUri solutionRepositoryUri); /** * Recreates BASE and SOLUTION Build Plan for the given programming exercise @@ -150,11 +150,11 @@ String copyBuildPlan(ProgrammingExercise sourceExercise, String sourcePlanName, * @param buildPlanKey The key of the build plan, which is usually the name combined with the project, e.g. 'EIST16W1-GA56HUR'. * @param ciRepoName The name of the configured repository in the CI plan, normally 'assignment' (or 'test'). * @param repoProjectKey The key of the project that contains the repository, e.g. 'EIST16W1', which is normally the programming exercise project key. - * @param newRepoUrl The url of the newly to be referenced repository. - * @param existingRepoUrl The url of the existing repository (which should be replaced). + * @param newRepoUri The url of the newly to be referenced repository. + * @param existingRepoUri The url of the existing repository (which should be replaced). * @param newBranch The default branch for the new repository */ - void updatePlanRepository(String buildProjectKey, String buildPlanKey, String ciRepoName, String repoProjectKey, String newRepoUrl, String existingRepoUrl, String newBranch); + void updatePlanRepository(String buildProjectKey, String buildPlanKey, String ciRepoName, String repoProjectKey, String newRepoUri, String existingRepoUri, String newBranch); /** * Gives overall roles permissions for the defined project. A role can e.g. be all logged-in users diff --git a/src/main/java/de/tum/in/www1/artemis/service/connectors/ci/ContinuousIntegrationUpdateService.java b/src/main/java/de/tum/in/www1/artemis/service/connectors/ci/ContinuousIntegrationUpdateService.java index 396c905b003e..d0b2c2504818 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/connectors/ci/ContinuousIntegrationUpdateService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/connectors/ci/ContinuousIntegrationUpdateService.java @@ -10,8 +10,8 @@ public interface ContinuousIntegrationUpdateService { * * @param buildPlanKey The key of the buildPlan, which is usually the username, e.g. 'ga56hur'. * @param ciRepoName The name of the configured repository in the continuous integration plan, normally 'assignment' or 'test' - * @param newRepoUrl The new repository URL + * @param newRepoUri The new repository URI * @param branchName The name of the default name of the repository */ - void updatePlanRepository(String buildPlanKey, String ciRepoName, String newRepoUrl, String branchName); + void updatePlanRepository(String buildPlanKey, String ciRepoName, String newRepoUri, String branchName); } diff --git a/src/main/java/de/tum/in/www1/artemis/service/connectors/gitlab/GitLabService.java b/src/main/java/de/tum/in/www1/artemis/service/connectors/gitlab/GitLabService.java index a704ace61d8b..fad7c0458008 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/connectors/gitlab/GitLabService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/connectors/gitlab/GitLabService.java @@ -40,7 +40,7 @@ import de.tum.in.www1.artemis.domain.participation.ProgrammingExerciseStudentParticipation; import de.tum.in.www1.artemis.exception.VersionControlException; import de.tum.in.www1.artemis.repository.*; -import de.tum.in.www1.artemis.service.UrlService; +import de.tum.in.www1.artemis.service.UriService; import de.tum.in.www1.artemis.service.connectors.ConnectorHealth; import de.tum.in.www1.artemis.service.connectors.GitService; import de.tum.in.www1.artemis.service.connectors.gitlab.dto.GitLabPushNotificationDTO; @@ -70,11 +70,11 @@ public class GitLabService extends AbstractVersionControlService { private final ScheduledExecutorService scheduler; - public GitLabService(UserRepository userRepository, @Qualifier("shortTimeoutGitlabRestTemplate") RestTemplate shortTimeoutRestTemplate, GitLabApi gitlab, UrlService urlService, + public GitLabService(UserRepository userRepository, @Qualifier("shortTimeoutGitlabRestTemplate") RestTemplate shortTimeoutRestTemplate, GitLabApi gitlab, UriService uriService, GitLabUserManagementService gitLabUserManagementService, GitService gitService, ApplicationContext applicationContext, ProgrammingExerciseStudentParticipationRepository studentParticipationRepository, ProgrammingExerciseRepository programmingExerciseRepository, TemplateProgrammingExerciseParticipationRepository templateProgrammingExerciseParticipationRepository) { - super(applicationContext, gitService, urlService, studentParticipationRepository, programmingExerciseRepository, templateProgrammingExerciseParticipationRepository); + super(gitService, uriService, studentParticipationRepository, programmingExerciseRepository, templateProgrammingExerciseParticipationRepository); this.userRepository = userRepository; this.shortTimeoutRestTemplate = shortTimeoutRestTemplate; this.gitlab = gitlab; @@ -94,7 +94,7 @@ public void configureRepository(ProgrammingExercise exercise, ProgrammingExercis if (allowAccess) { final VersionControlRepositoryPermission permissions = determineRepositoryPermissions(exercise); - addMemberToRepository(participation.getVcsRepositoryUrl(), user, permissions); + addMemberToRepository(participation.getVcsRepositoryUri(), user, permissions); } // Validate that the access token exist, if it is required @@ -103,12 +103,12 @@ public void configureRepository(ProgrammingExercise exercise, ProgrammingExercis // TODO: we should separate access (above) from protecting branches String branch = getOrRetrieveBranchOfStudentParticipation(participation); - protectBranch(participation.getVcsRepositoryUrl(), branch); + protectBranch(participation.getVcsRepositoryUri(), branch); } @Override - public void addMemberToRepository(VcsRepositoryUrl repositoryUrl, User user, VersionControlRepositoryPermission permissions) { - final String repositoryPath = urlService.getRepositoryPathFromRepositoryUrl(repositoryUrl); + public void addMemberToRepository(VcsRepositoryUri repositoryUri, User user, VersionControlRepositoryPermission permissions) { + final String repositoryPath = uriService.getRepositoryPathFromRepositoryUri(repositoryUri); final Long userId = gitLabUserManagementService.getUserId(user.getLogin()); final AccessLevel repositoryPermissions = permissionsToAccessLevel(permissions); @@ -120,14 +120,14 @@ public void addMemberToRepository(VcsRepositoryUrl repositoryUrl, User user, Ver // A resource conflict status code is returned if the member // already exists in the repository if (e.getHttpStatus() == 409) { - updateMemberPermissionInRepository(repositoryUrl, user, permissions); + updateMemberPermissionInRepository(repositoryUri, user, permissions); } else if (e.getValidationErrors() != null && e.getValidationErrors().containsKey("access_level") && e.getValidationErrors().get("access_level").stream().anyMatch(s -> s.contains("should be greater than or equal to"))) { log.warn("Member already has the requested permissions! Permission stays the same"); } else { - throw new GitLabException("Error while trying to add user to repository: " + user.getLogin() + " to repo " + repositoryUrl, e); + throw new GitLabException("Error while trying to add user to repository: " + user.getLogin() + " to repo " + repositoryUri, e); } } } @@ -140,27 +140,27 @@ private static AccessLevel permissionsToAccessLevel(final VersionControlReposito } @Override - public void removeMemberFromRepository(VcsRepositoryUrl repositoryUrl, User user) { - final var repositoryPath = urlService.getRepositoryPathFromRepositoryUrl(repositoryUrl); + public void removeMemberFromRepository(VcsRepositoryUri repositoryUri, User user) { + final var repositoryPath = uriService.getRepositoryPathFromRepositoryUri(repositoryUri); final var userId = gitLabUserManagementService.getUserId(user.getLogin()); try { gitlab.getProjectApi().removeMember(repositoryPath, userId); } catch (GitLabApiException e) { - throw new GitLabException("Error while trying to remove user from repository: " + user.getLogin() + " from repo " + repositoryUrl, e); + throw new GitLabException("Error while trying to remove user from repository: " + user.getLogin() + " from repo " + repositoryUri, e); } } /** * Get the default branch of the repository * - * @param repositoryUrl The repository url to get the default branch for. + * @param repositoryUri The repository uri to get the default branch for. * @return the name of the default branch, e.g. 'main' */ @Override - public String getDefaultBranchOfRepository(VcsRepositoryUrl repositoryUrl) throws GitLabException { - var repositoryId = getPathIDFromRepositoryURL(repositoryUrl); + public String getDefaultBranchOfRepository(VcsRepositoryUri repositoryUri) throws GitLabException { + var repositoryId = getPathIDFromRepositoryURL(repositoryUri); try { return gitlab.getProjectApi().getProject(repositoryId).getDefaultBranch(); @@ -170,8 +170,8 @@ public String getDefaultBranchOfRepository(VcsRepositoryUrl repositoryUrl) throw } } - private String getPathIDFromRepositoryURL(VcsRepositoryUrl repositoryUrl) { - final var namespaces = repositoryUrl.getURI().toString().split("/"); + private String getPathIDFromRepositoryURL(VcsRepositoryUri repositoryUri) { + final var namespaces = repositoryUri.getURI().toString().split("/"); final var last = namespaces.length - 1; return namespaces[last - 1] + "/" + namespaces[last].replace(".git", ""); } @@ -179,12 +179,12 @@ private String getPathIDFromRepositoryURL(VcsRepositoryUrl repositoryUrl) { /** * Protects a branch from the repository, so that developers cannot change the history * - * @param repositoryUrl The repository url of the repository to update. It contains the project key & the repository name. + * @param repositoryUri The repository uri of the repository to update. It contains the project key & the repository name. * @param branch The name of the branch to protect (e.g "main") * @throws VersionControlException If the communication with the VCS fails. */ - private void protectBranch(VcsRepositoryUrl repositoryUrl, String branch) { - final var repositoryPath = urlService.getRepositoryPathFromRepositoryUrl(repositoryUrl); + private void protectBranch(VcsRepositoryUri repositoryUri, String branch) { + final var repositoryPath = uriService.getRepositoryPathFromRepositoryUri(repositoryUri); // we have to first unprotect the branch in order to set the correct access level, this is the case, because the main branch is protected for maintainers by default // Unprotect the branch in 8 seconds first and then protect the branch in 12 seconds. // We do this to wait on any async calls to Gitlab and make sure that the branch really exists before protecting it. @@ -213,8 +213,8 @@ private void protectBranch(String repositoryPath, String branch, Long delayTime, } @Override - public void unprotectBranch(VcsRepositoryUrl repositoryUrl, String branch) throws VersionControlException { - final var repositoryPath = urlService.getRepositoryPathFromRepositoryUrl(repositoryUrl); + public void unprotectBranch(VcsRepositoryUri repositoryUri, String branch) throws VersionControlException { + final var repositoryPath = uriService.getRepositoryPathFromRepositoryUri(repositoryUri); // Unprotect the branch in 10 seconds. We do this to wait on any async calls to Gitlab and make sure that the branch really exists before unprotecting it. unprotectBranch(repositoryPath, branch, 10L, TimeUnit.SECONDS); } @@ -240,13 +240,13 @@ private void unprotectBranch(String repositoryPath, String branch, Long delayTim } @Override - protected void addWebHook(VcsRepositoryUrl repositoryUrl, String notificationUrl, String webHookName) { - addAuthenticatedWebHook(repositoryUrl, notificationUrl, webHookName, "noSecretNeeded"); + protected void addWebHook(VcsRepositoryUri repositoryUri, String notificationUrl, String webHookName) { + addAuthenticatedWebHook(repositoryUri, notificationUrl, webHookName, "noSecretNeeded"); } @Override - protected void addAuthenticatedWebHook(VcsRepositoryUrl repositoryUrl, String notificationUrl, String webHookName, String secretToken) { - final var repositoryPath = urlService.getRepositoryPathFromRepositoryUrl(repositoryUrl); + protected void addAuthenticatedWebHook(VcsRepositoryUri repositoryUri, String notificationUrl, String webHookName, String secretToken) { + final var repositoryPath = uriService.getRepositoryPathFromRepositoryUri(repositoryUri); final var hook = new ProjectHook().withPushEvents(true).withIssuesEvents(false).withMergeRequestsEvents(false).withWikiPageEvents(false) // Note: Trigger hook on push events for matching branches only (this avoids unnecessary Jenkins builds for pushes to other branches) .withPushEventsBranchFilter(defaultBranch); @@ -255,7 +255,7 @@ protected void addAuthenticatedWebHook(VcsRepositoryUrl repositoryUrl, String no gitlab.getProjectApi().addHook(repositoryPath, notificationUrl, hook, false, secretToken); } catch (GitLabApiException e) { - throw new GitLabException("Unable to add webhook for " + repositoryUrl, e); + throw new GitLabException("Unable to add webhook for " + repositoryUri, e); } } @@ -273,9 +273,9 @@ public void deleteProject(String projectKey) { } @Override - public void deleteRepository(VcsRepositoryUrl repositoryUrl) { - final var repositoryPath = urlService.getRepositoryPathFromRepositoryUrl(repositoryUrl); - final var repositoryName = urlService.getRepositorySlugFromRepositoryUrl(repositoryUrl); + public void deleteRepository(VcsRepositoryUri repositoryUri) { + final var repositoryPath = uriService.getRepositoryPathFromRepositoryUri(repositoryUri); + final var repositoryName = uriService.getRepositorySlugFromRepositoryUri(repositoryUri); try { gitlab.getProjectApi().deleteProject(repositoryPath); } @@ -288,21 +288,21 @@ public void deleteRepository(VcsRepositoryUrl repositoryUrl) { } @Override - public VcsRepositoryUrl getCloneRepositoryUrl(String projectKey, String repositorySlug) { - return new GitLabRepositoryUrl(projectKey, repositorySlug); + public VcsRepositoryUri getCloneRepositoryUri(String projectKey, String repositorySlug) { + return new GitLabRepositoryUri(projectKey, repositorySlug); } @Override - public Boolean repositoryUrlIsValid(@Nullable VcsRepositoryUrl repositoryUrl) { - if (repositoryUrl == null || repositoryUrl.getURI() == null) { + public Boolean repositoryUriIsValid(@Nullable VcsRepositoryUri repositoryUri) { + if (repositoryUri == null || repositoryUri.getURI() == null) { return false; } - final var repositoryPath = urlService.getRepositoryPathFromRepositoryUrl(repositoryUrl); + final var repositoryPath = uriService.getRepositoryPathFromRepositoryUri(repositoryUri); try { gitlab.getProjectApi().getProject(repositoryPath); } catch (Exception emAll) { - log.warn("Invalid repository VcsRepositoryUrl {}", repositoryUrl); + log.warn("Invalid repository VcsRepositoryUri {}", repositoryUri); return false; } @@ -333,8 +333,8 @@ public Commit getLastCommitDetails(Object requestBody) throws VersionControlExce public ZonedDateTime getPushDate(ProgrammingExerciseParticipation participation, String commitHash, Object eventObject) { // Gitlab never provides the push date initially so we can ignore the eventObject try { - String repositoryUrl = urlService.getRepositoryPathFromRepositoryUrl(participation.getVcsRepositoryUrl()); - Stream eventStream = gitlab.getEventsApi().getProjectEventsStream(repositoryUrl, Constants.ActionType.PUSHED, null, null, null, Constants.SortOrder.DESC); + String repositoryUri = uriService.getRepositoryPathFromRepositoryUri(participation.getVcsRepositoryUri()); + Stream eventStream = gitlab.getEventsApi().getProjectEventsStream(repositoryUri, Constants.ActionType.PUSHED, null, null, null, Constants.SortOrder.DESC); var eventOptional = eventStream.filter(event -> commitHash.equals(event.getPushData().getCommitTo())).findFirst(); if (eventOptional.isPresent()) { @@ -447,20 +447,20 @@ public boolean checkIfProjectExists(String projectKey, String projectName) { } @Override - public void setRepositoryPermissionsToReadOnly(VcsRepositoryUrl repositoryUrl, String projectKey, Set users) { - users.forEach(user -> updateMemberPermissionInRepository(repositoryUrl, user, VersionControlRepositoryPermission.REPO_READ)); + public void setRepositoryPermissionsToReadOnly(VcsRepositoryUri repositoryUri, String projectKey, Set users) { + users.forEach(user -> updateMemberPermissionInRepository(repositoryUri, user, VersionControlRepositoryPermission.REPO_READ)); } /** * Updates the access level of the user if it's a member of the repository. * - * @param repositoryUrl The url of the repository + * @param repositoryUri The url of the repository * @param user The GitLab user * @param permissions The new access level for the user */ - private void updateMemberPermissionInRepository(VcsRepositoryUrl repositoryUrl, User user, VersionControlRepositoryPermission permissions) { + private void updateMemberPermissionInRepository(VcsRepositoryUri repositoryUri, User user, VersionControlRepositoryPermission permissions) { final var userId = gitLabUserManagementService.getUserId(user.getLogin()); - final var repositoryPath = urlService.getRepositoryPathFromRepositoryUrl(repositoryUrl); + final var repositoryPath = uriService.getRepositoryPathFromRepositoryUri(repositoryUri); try { final Optional member = gitlab.getProjectApi().getOptionalMember(repositoryPath, userId); if (member.isPresent()) { @@ -468,7 +468,7 @@ private void updateMemberPermissionInRepository(VcsRepositoryUrl repositoryUrl, gitlab.getProjectApi().updateMember(repositoryPath, userId, accessLevel); } else { - addMemberToRepository(repositoryUrl, user, permissions); + addMemberToRepository(repositoryUri, user, permissions); } } catch (GitLabApiException e) { @@ -523,16 +523,16 @@ public UriComponentsBuilder buildEndpoint(String baseUrl, Object... args) { } } - public final class GitLabRepositoryUrl extends VcsRepositoryUrl { + public final class GitLabRepositoryUri extends VcsRepositoryUri { - public GitLabRepositoryUrl(String projectKey, String repositorySlug) { + public GitLabRepositoryUri(String projectKey, String repositorySlug) { final var path = projectKey + "/" + repositorySlug; final var urlString = gitlabServerUrl + "/" + path + ".git"; stringToURL(urlString); } - private GitLabRepositoryUrl(String urlString) { + private GitLabRepositoryUri(String urlString) { stringToURL(urlString); } @@ -546,9 +546,9 @@ private void stringToURL(String urlString) { } @Override - public VcsRepositoryUrl withUser(String username) { + public VcsRepositoryUri withUser(String username) { this.username = username; - return new GitLabRepositoryUrl(uri.toString().replaceAll("(https?://)(.*)", "$1" + username + "@$2")); + return new GitLabRepositoryUri(uri.toString().replaceAll("(https?://)(.*)", "$1" + username + "@$2")); } } } diff --git a/src/main/java/de/tum/in/www1/artemis/service/connectors/gitlabci/GitLabCIService.java b/src/main/java/de/tum/in/www1/artemis/service/connectors/gitlabci/GitLabCIService.java index 5a31f279ec2a..398ab8b6d326 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/connectors/gitlabci/GitLabCIService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/connectors/gitlabci/GitLabCIService.java @@ -23,13 +23,13 @@ import de.tum.in.www1.artemis.domain.BuildPlan; import de.tum.in.www1.artemis.domain.ProgrammingExercise; import de.tum.in.www1.artemis.domain.ProgrammingSubmission; -import de.tum.in.www1.artemis.domain.VcsRepositoryUrl; +import de.tum.in.www1.artemis.domain.VcsRepositoryUri; import de.tum.in.www1.artemis.domain.participation.ProgrammingExerciseParticipation; import de.tum.in.www1.artemis.exception.ContinuousIntegrationException; import de.tum.in.www1.artemis.exception.GitLabCIException; import de.tum.in.www1.artemis.repository.*; import de.tum.in.www1.artemis.service.BuildLogEntryService; -import de.tum.in.www1.artemis.service.UrlService; +import de.tum.in.www1.artemis.service.UriService; import de.tum.in.www1.artemis.service.connectors.ConnectorHealth; import de.tum.in.www1.artemis.service.connectors.ci.AbstractContinuousIntegrationService; import de.tum.in.www1.artemis.service.connectors.ci.CIPermission; @@ -70,7 +70,7 @@ public class GitLabCIService extends AbstractContinuousIntegrationService { private final GitLabApi gitlab; - private final UrlService urlService; + private final UriService uriService; private final BuildPlanRepository buildPlanRepository; @@ -97,27 +97,27 @@ public class GitLabCIService extends AbstractContinuousIntegrationService { private String gitlabToken; public GitLabCIService(ProgrammingSubmissionRepository programmingSubmissionRepository, FeedbackRepository feedbackRepository, BuildLogEntryService buildLogService, - GitLabApi gitlab, UrlService urlService, BuildPlanRepository buildPlanRepository, GitLabCIBuildPlanService buildPlanService, + GitLabApi gitlab, UriService uriService, BuildPlanRepository buildPlanRepository, GitLabCIBuildPlanService buildPlanService, ProgrammingLanguageConfiguration programmingLanguageConfiguration, BuildLogStatisticsEntryRepository buildLogStatisticsEntryRepository, TestwiseCoverageService testwiseCoverageService) { super(programmingSubmissionRepository, feedbackRepository, buildLogService, buildLogStatisticsEntryRepository, testwiseCoverageService); this.gitlab = gitlab; - this.urlService = urlService; + this.uriService = uriService; this.buildPlanRepository = buildPlanRepository; this.buildPlanService = buildPlanService; this.programmingLanguageConfiguration = programmingLanguageConfiguration; } @Override - public void createBuildPlanForExercise(ProgrammingExercise exercise, String planKey, VcsRepositoryUrl repositoryURL, VcsRepositoryUrl testRepositoryURL, - VcsRepositoryUrl solutionRepositoryURL) { + public void createBuildPlanForExercise(ProgrammingExercise exercise, String planKey, VcsRepositoryUri repositoryUri, VcsRepositoryUri testRepositoryUri, + VcsRepositoryUri solutionRepositoryUri) { addBuildPlanToProgrammingExerciseIfUnset(exercise); - setupGitLabCIConfiguration(repositoryURL, exercise, planKey); - // TODO: triggerBuild(repositoryURL, exercise.getBranch()); + setupGitLabCIConfiguration(repositoryUri, exercise, planKey); + // TODO: triggerBuild(repositoryUri, exercise.getBranch()); } - private void setupGitLabCIConfiguration(VcsRepositoryUrl repositoryURL, ProgrammingExercise exercise, String buildPlanId) { - final String repositoryPath = urlService.getRepositoryPathFromRepositoryUrl(repositoryURL); + private void setupGitLabCIConfiguration(VcsRepositoryUri repositoryUri, ProgrammingExercise exercise, String buildPlanId) { + final String repositoryPath = uriService.getRepositoryPathFromRepositoryUri(repositoryUri); ProjectApi projectApi = gitlab.getProjectApi(); try { Project project = projectApi.getProject(repositoryPath); @@ -132,7 +132,7 @@ private void setupGitLabCIConfiguration(VcsRepositoryUrl repositoryURL, Programm projectApi.updateProject(project); } catch (GitLabApiException e) { - throw new GitLabCIException("Error enabling CI for " + repositoryURL.toString(), e); + throw new GitLabCIException("Error enabling CI for " + repositoryUri, e); } try { @@ -149,14 +149,14 @@ private void setupGitLabCIConfiguration(VcsRepositoryUrl repositoryURL, Programm updateVariable(repositoryPath, VARIABLE_NOTIFICATION_URL_NAME, artemisServerUrl.toExternalForm() + NEW_RESULT_RESOURCE_API_PATH); updateVariable(repositoryPath, VARIABLE_SUBMISSION_GIT_BRANCH_NAME, exercise.getBranch()); updateVariable(repositoryPath, VARIABLE_TEST_GIT_BRANCH_NAME, exercise.getBranch()); - updateVariable(repositoryPath, VARIABLE_TEST_GIT_REPOSITORY_SLUG_NAME, urlService.getRepositorySlugFromRepositoryUrlString(exercise.getTestRepositoryUrl())); + updateVariable(repositoryPath, VARIABLE_TEST_GIT_REPOSITORY_SLUG_NAME, uriService.getRepositorySlugFromRepositoryUriString(exercise.getTestRepositoryUri())); // TODO: Use a token that is only valid for the test repository for each programming exercise updateVariable(repositoryPath, VARIABLE_TEST_GIT_TOKEN, gitlabToken); updateVariable(repositoryPath, VARIABLE_TEST_GIT_USER, gitlabUser); updateVariable(repositoryPath, VARIABLE_TEST_RESULTS_DIR_NAME, "target/surefire-reports"); } catch (GitLabApiException e) { - log.error("Error creating variable for {} The variables may already have been created.", repositoryURL, e); + log.error("Error creating variable for {} The variables may already have been created.", repositoryUri, e); } } @@ -183,11 +183,11 @@ private void addBuildPlanToProgrammingExerciseIfUnset(ProgrammingExercise progra public void recreateBuildPlansForExercise(ProgrammingExercise exercise) { addBuildPlanToProgrammingExerciseIfUnset(exercise); - VcsRepositoryUrl templateUrl = exercise.getVcsTemplateRepositoryUrl(); + VcsRepositoryUri templateUrl = exercise.getVcsTemplateRepositoryUri(); setupGitLabCIConfiguration(templateUrl, exercise, exercise.getTemplateBuildPlanId()); // TODO: triggerBuild(templateUrl, exercise.getBranch()); - VcsRepositoryUrl solutionUrl = exercise.getVcsSolutionRepositoryUrl(); + VcsRepositoryUri solutionUrl = exercise.getVcsSolutionRepositoryUri(); setupGitLabCIConfiguration(solutionUrl, exercise, exercise.getSolutionBuildPlanId()); // TODO: triggerBuild(solutionUrl, exercise.getBranch()); } @@ -205,7 +205,7 @@ public String copyBuildPlan(ProgrammingExercise sourceExercise, String sourcePla @Override public void configureBuildPlan(ProgrammingExerciseParticipation participation, String defaultBranch) { - setupGitLabCIConfiguration(participation.getVcsRepositoryUrl(), participation.getProgrammingExercise(), participation.getBuildPlanId()); + setupGitLabCIConfiguration(participation.getVcsRepositoryUri(), participation.getProgrammingExercise(), participation.getBuildPlanId()); } @Override @@ -249,7 +249,7 @@ private BuildStatus convertPipelineStatusToBuildStatus(PipelineStatus status) { } private Optional getLatestPipeline(final ProgrammingExerciseParticipation participation) throws GitLabApiException { - final String repositoryPath = urlService.getRepositoryPathFromRepositoryUrl(participation.getVcsRepositoryUrl()); + final String repositoryPath = uriService.getRepositoryPathFromRepositoryUri(participation.getVcsRepositoryUri()); final Optional commitHash = participation.findLatestSubmission().map(ProgrammingSubmission.class::cast).map(ProgrammingSubmission::getCommitHash); if (commitHash.isEmpty()) { return Optional.empty(); @@ -282,7 +282,7 @@ public void enablePlan(String projectKey, String planKey) { } @Override - public void updatePlanRepository(String buildProjectKey, String buildPlanKey, String ciRepoName, String repoProjectKey, String newRepoUrl, String existingRepoUrl, + public void updatePlanRepository(String buildProjectKey, String buildPlanKey, String ciRepoName, String repoProjectKey, String newRepoUri, String existingRepoUri, String newDefaultBranch) { log.error("Unsupported action: GitLabCIService.updatePlanRepository()"); } diff --git a/src/main/java/de/tum/in/www1/artemis/service/connectors/gitlabci/GitLabCITriggerService.java b/src/main/java/de/tum/in/www1/artemis/service/connectors/gitlabci/GitLabCITriggerService.java index 5d197f3e219e..099e76e55ccd 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/connectors/gitlabci/GitLabCITriggerService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/connectors/gitlabci/GitLabCITriggerService.java @@ -6,11 +6,11 @@ import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Service; -import de.tum.in.www1.artemis.domain.VcsRepositoryUrl; +import de.tum.in.www1.artemis.domain.VcsRepositoryUri; import de.tum.in.www1.artemis.domain.participation.ProgrammingExerciseParticipation; import de.tum.in.www1.artemis.exception.ContinuousIntegrationException; import de.tum.in.www1.artemis.exception.GitLabCIException; -import de.tum.in.www1.artemis.service.UrlService; +import de.tum.in.www1.artemis.service.UriService; import de.tum.in.www1.artemis.service.connectors.ci.ContinuousIntegrationTriggerService; @Profile("gitlabci") @@ -19,20 +19,20 @@ public class GitLabCITriggerService implements ContinuousIntegrationTriggerServi private final GitLabApi gitlab; - private final UrlService urlService; + private final UriService uriService; - public GitLabCITriggerService(GitLabApi gitlab, UrlService urlService) { + public GitLabCITriggerService(GitLabApi gitlab, UriService uriService) { this.gitlab = gitlab; - this.urlService = urlService; + this.uriService = uriService; } @Override public void triggerBuild(ProgrammingExerciseParticipation participation) throws ContinuousIntegrationException { - triggerBuild(participation.getVcsRepositoryUrl(), participation.getProgrammingExercise().getBranch()); + triggerBuild(participation.getVcsRepositoryUri(), participation.getProgrammingExercise().getBranch()); } - private void triggerBuild(VcsRepositoryUrl vcsRepositoryUrl, String branch) { - final String repositoryPath = urlService.getRepositoryPathFromRepositoryUrl(vcsRepositoryUrl); + private void triggerBuild(VcsRepositoryUri vcsRepositoryUri, String branch) { + final String repositoryPath = uriService.getRepositoryPathFromRepositoryUri(vcsRepositoryUri); try { Trigger trigger = gitlab.getPipelineApi().createPipelineTrigger(repositoryPath, "Trigger build"); gitlab.getPipelineApi().triggerPipeline(repositoryPath, trigger, branch, null); diff --git a/src/main/java/de/tum/in/www1/artemis/service/connectors/jenkins/JenkinsService.java b/src/main/java/de/tum/in/www1/artemis/service/connectors/jenkins/JenkinsService.java index 2cef017be790..d0bdf15b4179 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/connectors/jenkins/JenkinsService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/connectors/jenkins/JenkinsService.java @@ -19,7 +19,7 @@ import com.offbytwo.jenkins.JenkinsServer; import de.tum.in.www1.artemis.domain.ProgrammingExercise; -import de.tum.in.www1.artemis.domain.VcsRepositoryUrl; +import de.tum.in.www1.artemis.domain.VcsRepositoryUri; import de.tum.in.www1.artemis.domain.enumeration.BuildPlanType; import de.tum.in.www1.artemis.domain.enumeration.RepositoryType; import de.tum.in.www1.artemis.domain.participation.ProgrammingExerciseParticipation; @@ -72,9 +72,9 @@ public JenkinsService(JenkinsServer jenkinsServer, ProgrammingSubmissionReposito } @Override - public void createBuildPlanForExercise(ProgrammingExercise exercise, String planKey, VcsRepositoryUrl repositoryURL, VcsRepositoryUrl testRepositoryURL, - VcsRepositoryUrl solutionRepositoryURL) { - jenkinsBuildPlanService.createBuildPlanForExercise(exercise, planKey, repositoryURL); + public void createBuildPlanForExercise(ProgrammingExercise exercise, String planKey, VcsRepositoryUri repositoryUri, VcsRepositoryUri testRepositoryUri, + VcsRepositoryUri solutionRepositoryUri) { + jenkinsBuildPlanService.createBuildPlanForExercise(exercise, planKey, repositoryUri); } @Override @@ -109,9 +109,9 @@ public void configureBuildPlan(ProgrammingExerciseParticipation participation, S } @Override - public void updatePlanRepository(String buildProjectKey, String buildPlanKey, String ciRepoName, String repoProjectKey, String newRepoUrl, String existingRepoUrl, + public void updatePlanRepository(String buildProjectKey, String buildPlanKey, String ciRepoName, String repoProjectKey, String newRepoUri, String existingRepoUri, String newDefaultBranch) { - jenkinsBuildPlanService.updateBuildPlanRepositories(buildProjectKey, buildPlanKey, newRepoUrl, existingRepoUrl); + jenkinsBuildPlanService.updateBuildPlanRepositories(buildProjectKey, buildPlanKey, newRepoUri, existingRepoUri); } @Override diff --git a/src/main/java/de/tum/in/www1/artemis/service/connectors/jenkins/JenkinsXmlConfigBuilder.java b/src/main/java/de/tum/in/www1/artemis/service/connectors/jenkins/JenkinsXmlConfigBuilder.java index 3734f2f3a2ff..f3be82b5d662 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/connectors/jenkins/JenkinsXmlConfigBuilder.java +++ b/src/main/java/de/tum/in/www1/artemis/service/connectors/jenkins/JenkinsXmlConfigBuilder.java @@ -4,7 +4,7 @@ import org.w3c.dom.Document; -import de.tum.in.www1.artemis.domain.VcsRepositoryUrl; +import de.tum.in.www1.artemis.domain.VcsRepositoryUri; import de.tum.in.www1.artemis.domain.enumeration.ProgrammingLanguage; import de.tum.in.www1.artemis.domain.enumeration.ProjectType; @@ -13,17 +13,17 @@ public interface JenkinsXmlConfigBuilder { /** * Contains the URLs required to set up the build plan. *

- * The URLs should haven been converted to the internal URL format using {@link JenkinsInternalUrlService#toInternalVcsUrl(VcsRepositoryUrl)}. + * The URLs should haven been converted to the internal URL format using {@link JenkinsInternalUrlService#toInternalVcsUrl(VcsRepositoryUri)}. * - * @param assignmentRepositoryUrl The URL to the VCS repository of the student. - * @param testRepositoryUrl The URL to the VCS repository of the tests for the exercise. - * @param solutionRepositoryUrl The URL to the VCS repository of the solution of the exercise. + * @param assignmentRepositoryUri The URL to the VCS repository of the student. + * @param testRepositoryUri The URL to the VCS repository of the tests for the exercise. + * @param solutionRepositoryUri The URL to the VCS repository of the solution of the exercise. */ - record InternalVcsRepositoryURLs(VcsRepositoryUrl assignmentRepositoryUrl, VcsRepositoryUrl testRepositoryUrl, VcsRepositoryUrl solutionRepositoryUrl) { + record InternalVcsRepositoryURLs(VcsRepositoryUri assignmentRepositoryUri, VcsRepositoryUri testRepositoryUri, VcsRepositoryUri solutionRepositoryUri) { } /** - * Creates a basic build config for Jenkins based on the given repository URLs. I.e. a build that tests the assignment + * Creates a basic build config for Jenkins based on the given repository URIs. I.e. a build that tests the assignment * code and exports the build results to Artemis afterwards. If static code analysis is activated, the plan will additionally * execute supported static code analysis tools. * diff --git a/src/main/java/de/tum/in/www1/artemis/service/connectors/jenkins/build_plan/JenkinsBuildPlanCreator.java b/src/main/java/de/tum/in/www1/artemis/service/connectors/jenkins/build_plan/JenkinsBuildPlanCreator.java index a24727dca8a9..de9fff3e4ba1 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/connectors/jenkins/build_plan/JenkinsBuildPlanCreator.java +++ b/src/main/java/de/tum/in/www1/artemis/service/connectors/jenkins/build_plan/JenkinsBuildPlanCreator.java @@ -137,9 +137,9 @@ private String makeSafeForXml(final String script) { private Map getReplacements(final InternalVcsRepositoryURLs internalVcsRepositoryURLs, final boolean checkoutSolution, final String buildPlanUrl) { final Map replacements = new HashMap<>(); - replacements.put(REPLACE_TEST_REPO, internalVcsRepositoryURLs.testRepositoryUrl().getURI().toString()); - replacements.put(REPLACE_ASSIGNMENT_REPO, internalVcsRepositoryURLs.assignmentRepositoryUrl().getURI().toString()); - replacements.put(REPLACE_SOLUTION_REPO, internalVcsRepositoryURLs.solutionRepositoryUrl().getURI().toString()); + replacements.put(REPLACE_TEST_REPO, internalVcsRepositoryURLs.testRepositoryUri().getURI().toString()); + replacements.put(REPLACE_ASSIGNMENT_REPO, internalVcsRepositoryURLs.assignmentRepositoryUri().getURI().toString()); + replacements.put(REPLACE_SOLUTION_REPO, internalVcsRepositoryURLs.solutionRepositoryUri().getURI().toString()); replacements.put(REPLACE_GIT_CREDENTIALS, gitCredentialsKey); replacements.put(REPLACE_ASSIGNMENT_CHECKOUT_PATH, Constants.ASSIGNMENT_CHECKOUT_PATH); replacements.put(REPLACE_TESTS_CHECKOUT_PATH, Constants.TESTS_CHECKOUT_PATH); diff --git a/src/main/java/de/tum/in/www1/artemis/service/connectors/jenkins/build_plan/JenkinsBuildPlanService.java b/src/main/java/de/tum/in/www1/artemis/service/connectors/jenkins/build_plan/JenkinsBuildPlanService.java index adfbae97df68..d620a543ce39 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/connectors/jenkins/build_plan/JenkinsBuildPlanService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/connectors/jenkins/build_plan/JenkinsBuildPlanService.java @@ -102,16 +102,16 @@ public JenkinsBuildPlanService(@Qualifier("jenkinsRestTemplate") RestTemplate re * * @param exercise the programming exercise * @param planKey the name of the plan - * @param repositoryURL the url of the vcs repository + * @param repositoryUri the uri of the vcs repository */ - public void createBuildPlanForExercise(ProgrammingExercise exercise, String planKey, VcsRepositoryUrl repositoryURL) { - final JenkinsXmlConfigBuilder.InternalVcsRepositoryURLs internalRepositoryUrls = getInternalRepositoryUrls(exercise, repositoryURL); + public void createBuildPlanForExercise(ProgrammingExercise exercise, String planKey, VcsRepositoryUri repositoryUri) { + final JenkinsXmlConfigBuilder.InternalVcsRepositoryURLs internalRepositoryUris = getInternalRepositoryUris(exercise, repositoryUri); final ProgrammingLanguage programmingLanguage = exercise.getProgrammingLanguage(); final var configBuilder = builderFor(programmingLanguage, exercise.getProjectType()); final String buildPlanUrl = jenkinsPipelineScriptCreator.generateBuildPlanURL(exercise); final boolean checkoutSolution = exercise.getCheckoutSolutionRepository(); - final Document jobConfig = configBuilder.buildBasicConfig(programmingLanguage, Optional.ofNullable(exercise.getProjectType()), internalRepositoryUrls, checkoutSolution, + final Document jobConfig = configBuilder.buildBasicConfig(programmingLanguage, Optional.ofNullable(exercise.getProjectType()), internalRepositoryUris, checkoutSolution, buildPlanUrl); // create build plan in database first, otherwise the job in Jenkins cannot find it for the initial build @@ -125,10 +125,10 @@ public void createBuildPlanForExercise(ProgrammingExercise exercise, String plan triggerBuild(jobFolder, job); } - private JenkinsXmlConfigBuilder.InternalVcsRepositoryURLs getInternalRepositoryUrls(final ProgrammingExercise exercise, final VcsRepositoryUrl assignmentRepositoryUrl) { - final VcsRepositoryUrl assignmentUrl = jenkinsInternalUrlService.toInternalVcsUrl(assignmentRepositoryUrl); - final VcsRepositoryUrl testUrl = jenkinsInternalUrlService.toInternalVcsUrl(exercise.getRepositoryURL(RepositoryType.TESTS)); - final VcsRepositoryUrl solutionUrl = jenkinsInternalUrlService.toInternalVcsUrl(exercise.getRepositoryURL(RepositoryType.SOLUTION)); + private JenkinsXmlConfigBuilder.InternalVcsRepositoryURLs getInternalRepositoryUris(final ProgrammingExercise exercise, final VcsRepositoryUri assignmentRepositoryUri) { + final VcsRepositoryUri assignmentUrl = jenkinsInternalUrlService.toInternalVcsUrl(assignmentRepositoryUri); + final VcsRepositoryUri testUrl = jenkinsInternalUrlService.toInternalVcsUrl(exercise.getRepositoryURL(RepositoryType.TESTS)); + final VcsRepositoryUri solutionUrl = jenkinsInternalUrlService.toInternalVcsUrl(exercise.getRepositoryURL(RepositoryType.SOLUTION)); return new JenkinsXmlConfigBuilder.InternalVcsRepositoryURLs(assignmentUrl, testUrl, solutionUrl); } @@ -166,8 +166,8 @@ public void configureBuildPlanForParticipation(ProgrammingExerciseParticipation String projectKey = programmingExercise.getProjectKey(); String planKey = participation.getBuildPlanId(); - String templateRepoUrl = programmingExercise.getTemplateRepositoryUrl(); - updateBuildPlanRepositories(projectKey, planKey, participation.getRepositoryUrl(), templateRepoUrl); + String templateRepoUri = programmingExercise.getTemplateRepositoryUri(); + updateBuildPlanRepositories(projectKey, planKey, participation.getRepositoryUri(), templateRepoUri); enablePlan(projectKey, planKey); // Students currently always have access to the build plan in Jenkins @@ -178,19 +178,19 @@ public void configureBuildPlanForParticipation(ProgrammingExerciseParticipation * * @param buildProjectKey the project key of the programming exercise * @param buildPlanKey the build plan id of the participation - * @param newRepoUrl the repository url that will replace the old url - * @param existingRepoUrl the old repository url that will be replaced + * @param newRepoUri the repository uri that will replace the old url + * @param existingRepoUri the old repository uri that will be replaced */ - public void updateBuildPlanRepositories(String buildProjectKey, String buildPlanKey, String newRepoUrl, String existingRepoUrl) { - newRepoUrl = jenkinsInternalUrlService.toInternalVcsUrl(newRepoUrl); - existingRepoUrl = jenkinsInternalUrlService.toInternalVcsUrl(existingRepoUrl); + public void updateBuildPlanRepositories(String buildProjectKey, String buildPlanKey, String newRepoUri, String existingRepoUri) { + newRepoUri = jenkinsInternalUrlService.toInternalVcsUrl(newRepoUri); + existingRepoUri = jenkinsInternalUrlService.toInternalVcsUrl(existingRepoUri); - // remove potential username from repo URL. Jenkins uses the Artemis Admin user and will fail if other usernames are in the URL - final var repoUrl = newRepoUrl.replaceAll("(https?://)(.*@)(.*)", "$1$3"); + // remove potential username from repo URI. Jenkins uses the Artemis Admin user and will fail if other usernames are in the URI + final var repoUri = newRepoUri.replaceAll("(https?://)(.*@)(.*)", "$1$3"); final Document jobConfig = jenkinsJobService.getJobConfigForJobInFolder(buildProjectKey, buildPlanKey); try { - JenkinsBuildPlanUtils.replaceScriptParameters(jobConfig, existingRepoUrl, repoUrl); + JenkinsBuildPlanUtils.replaceScriptParameters(jobConfig, existingRepoUri, repoUri); } catch (IllegalArgumentException e) { log.error("Pipeline Script not found", e); diff --git a/src/main/java/de/tum/in/www1/artemis/service/connectors/jenkins/build_plan/JenkinsBuildPlanUtils.java b/src/main/java/de/tum/in/www1/artemis/service/connectors/jenkins/build_plan/JenkinsBuildPlanUtils.java index 834d73ab52b3..f4cd04d35e8b 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/connectors/jenkins/build_plan/JenkinsBuildPlanUtils.java +++ b/src/main/java/de/tum/in/www1/artemis/service/connectors/jenkins/build_plan/JenkinsBuildPlanUtils.java @@ -8,7 +8,7 @@ public class JenkinsBuildPlanUtils { private static final String PIPELINE_SCRIPT_DETECTION_COMMENT = "// ARTEMIS: JenkinsPipeline"; /** - * Replaces either one of the previous repository urls or the build plan url written within the Jenkins pipeline + * Replaces either one of the previous repository uris or the build plan url written within the Jenkins pipeline * script with the value specified by newUrl. * * @param jobXmlDocument the Jenkins pipeline diff --git a/src/main/java/de/tum/in/www1/artemis/service/connectors/localci/LocalCIBuildJobExecutionService.java b/src/main/java/de/tum/in/www1/artemis/service/connectors/localci/LocalCIBuildJobExecutionService.java index 3c4cd6963fbb..b2da7f0cca57 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/connectors/localci/LocalCIBuildJobExecutionService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/connectors/localci/LocalCIBuildJobExecutionService.java @@ -50,7 +50,7 @@ import de.tum.in.www1.artemis.service.connectors.localci.scaparser.exception.UnsupportedToolException; import de.tum.in.www1.artemis.service.connectors.localci.scaparser.strategy.ParserPolicy; import de.tum.in.www1.artemis.service.connectors.localci.scaparser.strategy.ParserStrategy; -import de.tum.in.www1.artemis.service.connectors.localvc.LocalVCRepositoryUrl; +import de.tum.in.www1.artemis.service.connectors.localvc.LocalVCRepositoryUri; import de.tum.in.www1.artemis.service.connectors.vcs.VersionControlService; import de.tum.in.www1.artemis.service.dto.StaticCodeAnalysisReportDTO; import de.tum.in.www1.artemis.service.programming.ProgrammingLanguageFeature; @@ -179,24 +179,24 @@ public LocalCIBuildResult runBuildJob(ProgrammingExerciseParticipation participa // Retrieve the paths to the repositories that the build job needs. // This includes the assignment repository (the one to be tested, e.g. the student's repository, or the template repository), and the tests repository which includes // the tests to be executed. - LocalVCRepositoryUrl assignmentRepositoryUrl; - LocalVCRepositoryUrl testsRepositoryUrl; - LocalVCRepositoryUrl[] auxiliaryRepositoriesUrls; + LocalVCRepositoryUri assignmentRepositoryUri; + LocalVCRepositoryUri testsRepositoryUri; + LocalVCRepositoryUri[] auxiliaryRepositoriesUris; Path[] auxiliaryRepositoriesPaths; String[] auxiliaryRepositoryCheckoutDirectories; try { - assignmentRepositoryUrl = new LocalVCRepositoryUrl(participation.getRepositoryUrl(), localVCBaseUrl); - testsRepositoryUrl = new LocalVCRepositoryUrl(participation.getProgrammingExercise().getTestRepositoryUrl(), localVCBaseUrl); + assignmentRepositoryUri = new LocalVCRepositoryUri(participation.getRepositoryUri(), localVCBaseUrl); + testsRepositoryUri = new LocalVCRepositoryUri(participation.getProgrammingExercise().getTestRepositoryUri(), localVCBaseUrl); if (!auxiliaryRepositories.isEmpty()) { - auxiliaryRepositoriesUrls = new LocalVCRepositoryUrl[auxiliaryRepositories.size()]; + auxiliaryRepositoriesUris = new LocalVCRepositoryUri[auxiliaryRepositories.size()]; auxiliaryRepositoriesPaths = new Path[auxiliaryRepositories.size()]; auxiliaryRepositoryCheckoutDirectories = new String[auxiliaryRepositories.size()]; for (int i = 0; i < auxiliaryRepositories.size(); i++) { - auxiliaryRepositoriesUrls[i] = new LocalVCRepositoryUrl(auxiliaryRepositories.get(i).getRepositoryUrl(), localVCBaseUrl); - auxiliaryRepositoriesPaths[i] = auxiliaryRepositoriesUrls[i].getRepoClonePath(repoClonePath).toAbsolutePath(); + auxiliaryRepositoriesUris[i] = new LocalVCRepositoryUri(auxiliaryRepositories.get(i).getRepositoryUri(), localVCBaseUrl); + auxiliaryRepositoriesPaths[i] = auxiliaryRepositoriesUris[i].getRepoClonePath(repoClonePath).toAbsolutePath(); auxiliaryRepositoryCheckoutDirectories[i] = auxiliaryRepositories.get(i).getCheckoutDirectory(); } } @@ -206,11 +206,11 @@ public LocalCIBuildResult runBuildJob(ProgrammingExerciseParticipation participa } } catch (LocalVCInternalException e) { - throw new LocalCIException("Error while creating LocalVCRepositoryUrl", e); + throw new LocalCIException("Error while creating LocalVCRepositoryUri", e); } - Path assignmentRepositoryPath = assignmentRepositoryUrl.getRepoClonePath(repoClonePath).toAbsolutePath(); - Path testsRepositoryPath = testsRepositoryUrl.getRepoClonePath(repoClonePath).toAbsolutePath(); + Path assignmentRepositoryPath = assignmentRepositoryUri.getRepoClonePath(repoClonePath).toAbsolutePath(); + Path testsRepositoryPath = testsRepositoryUri.getRepoClonePath(repoClonePath).toAbsolutePath(); Path solutionRepositoryPath = null; if (participation.getProgrammingExercise().getCheckoutSolutionRepository()) { try { @@ -220,14 +220,14 @@ public LocalCIBuildResult runBuildJob(ProgrammingExerciseParticipation participa if (programmingLanguageFeature.checkoutSolutionRepositoryAllowed()) { var solutionParticipation = solutionProgrammingExerciseParticipationRepository.findByProgrammingExerciseId(participation.getProgrammingExercise().getId()); if (solutionParticipation.isPresent()) { - solutionRepositoryPath = new LocalVCRepositoryUrl(solutionParticipation.get().getRepositoryUrl(), localVCBaseUrl).getRepoClonePath(repoClonePath) + solutionRepositoryPath = new LocalVCRepositoryUri(solutionParticipation.get().getRepositoryUri(), localVCBaseUrl).getRepoClonePath(repoClonePath) .toAbsolutePath(); } } } } catch (Exception e) { - throw new LocalCIException("Error while creating solution LocalVCRepositoryUrl", e); + throw new LocalCIException("Error while creating solution LocalVCRepositoryUri", e); } } @@ -347,7 +347,7 @@ private LocalCIBuildResult runScriptAndParseResults(ProgrammingExerciseParticipa // Set the build status to "INACTIVE" to indicate that the build is not running anymore. localCIBuildPlanService.updateBuildPlanStatus(participation, ContinuousIntegrationService.BuildStatus.INACTIVE); - log.info("Building and testing submission for repository {} and commit hash {} took {}", participation.getRepositoryUrl(), commitHash, + log.info("Building and testing submission for repository {} and commit hash {} took {}", participation.getRepositoryUri(), commitHash, TimeLogUtil.formatDurationFrom(timeNanoStart)); return buildResult; @@ -619,7 +619,7 @@ private LocalCIBuildResult constructBuildResult(List createCompletableFuture(Supplier users) { + public void setRepositoryPermissionsToReadOnly(VcsRepositoryUri repositoryUri, String projectKey, Set users) { // Not implemented for local VC. All checks for whether a student can access a repository are conducted in the LocalVCFetchFilter and LocalVCPushFilter. } /** * Get the default branch of the repository * - * @param repositoryUrl The repository url to get the default branch for. + * @param repositoryUri The repository uri to get the default branch for. * @return the name of the default branch, e.g. 'main' * @throws LocalVCInternalException if the default branch cannot be determined */ @Override - public String getDefaultBranchOfRepository(VcsRepositoryUrl repositoryUrl) { - LocalVCRepositoryUrl localVCRepositoryUrl = new LocalVCRepositoryUrl(repositoryUrl.toString(), localVCBaseUrl); - String localRepositoryPath = localVCRepositoryUrl.getLocalRepositoryPath(localVCBasePath).toString(); + public String getDefaultBranchOfRepository(VcsRepositoryUri repositoryUri) { + LocalVCRepositoryUri localVCRepositoryUri = new LocalVCRepositoryUri(repositoryUri.toString(), localVCBaseUrl); + String localRepositoryPath = localVCRepositoryUri.getLocalRepositoryPath(localVCBasePath).toString(); Map remoteRepositoryRefs; try { remoteRepositoryRefs = Git.lsRemoteRepository().setRemote(localRepositoryPath).callAsMap(); @@ -172,7 +172,7 @@ public String getDefaultBranchOfRepository(VcsRepositoryUrl repositoryUrl) { } @Override - public void unprotectBranch(VcsRepositoryUrl repositoryUrl, String branch) { + public void unprotectBranch(VcsRepositoryUri repositoryUri, String branch) { // Not implemented. It is not needed for local VC for the current use // case, because the main branch is unprotected by default. } @@ -233,9 +233,9 @@ public void createRepository(String projectKey, String repositorySlug, String pa private void createRepository(String projectKey, String repositorySlug) { - LocalVCRepositoryUrl localVCRepositoryUrl = new LocalVCRepositoryUrl(projectKey, repositorySlug, localVCBaseUrl); + LocalVCRepositoryUri localVCRepositoryUri = new LocalVCRepositoryUri(projectKey, repositorySlug, localVCBaseUrl); - Path remoteDirPath = localVCRepositoryUrl.getLocalRepositoryPath(localVCBasePath); + Path remoteDirPath = localVCRepositoryUri.getLocalRepositoryPath(localVCBasePath); try { Files.createDirectories(remoteDirPath); @@ -259,13 +259,13 @@ private void createRepository(String projectKey, String repositorySlug) { } @Override - public Boolean repositoryUrlIsValid(@Nullable VcsRepositoryUrl repositoryUrl) { - if (repositoryUrl == null || repositoryUrl.getURI() == null) { + public Boolean repositoryUriIsValid(@Nullable VcsRepositoryUri repositoryUri) { + if (repositoryUri == null || repositoryUri.getURI() == null) { return false; } try { - new LocalVCRepositoryUrl(repositoryUrl.toString(), localVCBaseUrl); + new LocalVCRepositoryUri(repositoryUri.toString(), localVCBaseUrl); } catch (LocalVCInternalException e) { return false; @@ -301,7 +301,7 @@ public ZonedDateTime getPushDate(ProgrammingExerciseParticipation participation, repository = gitService.getOrCheckoutRepository(participation); } catch (GitAPIException e) { - throw new LocalVCInternalException("Unable to get the repository from participation " + participation.getId() + ": " + participation.getRepositoryUrl(), e); + throw new LocalVCInternalException("Unable to get the repository from participation " + participation.getId() + ": " + participation.getRepositoryUri(), e); } try (RevWalk revWalk = new RevWalk(repository)) { @@ -313,7 +313,7 @@ public ZonedDateTime getPushDate(ProgrammingExerciseParticipation participation, return ZonedDateTime.ofInstant(instant, zoneId); } catch (IOException e) { - throw new LocalVCInternalException("Unable to get the push date from participation " + participation.getId() + ": " + participation.getRepositoryUrl(), e); + throw new LocalVCInternalException("Unable to get the push date from participation " + participation.getId() + ": " + participation.getRepositoryUri(), e); } } } diff --git a/src/main/java/de/tum/in/www1/artemis/service/connectors/localvc/LocalVCServletService.java b/src/main/java/de/tum/in/www1/artemis/service/connectors/localvc/LocalVCServletService.java index bd1fd437f59d..b8c4d8bd0ce8 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/connectors/localvc/LocalVCServletService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/connectors/localvc/LocalVCServletService.java @@ -159,7 +159,7 @@ public Repository resolveRepository(String repositoryPath) throws RepositoryNotF * @param repositoryAction Indicates whether the method should authenticate a fetch or a push request. For a push request, additional checks are conducted. * @throws LocalVCAuthException If the user authentication fails or the user is not authorized to access a certain repository. * @throws LocalVCForbiddenException If the user is not allowed to access the repository, e.g. because offline IDE usage is not allowed or the due date has passed. - * @throws LocalVCInternalException If an internal error occurs, e.g. because the LocalVCRepositoryUrl could not be created. + * @throws LocalVCInternalException If an internal error occurs, e.g. because the LocalVCRepositoryUri could not be created. */ public void authenticateAndAuthorizeGitRequest(HttpServletRequest request, RepositoryActionType repositoryAction) throws LocalVCAuthException, LocalVCForbiddenException { @@ -169,7 +169,7 @@ public void authenticateAndAuthorizeGitRequest(HttpServletRequest request, Repos // Optimization. // For each git command (i.e. 'git fetch' or 'git push'), the git client sends three requests. - // The URLs of the first two requests end on '[repository URL]/info/refs'. The third one ends on '[repository URL]/git-receive-pack' (for push) and '[repository + // The URLs of the first two requests end on '[repository URI]/info/refs'. The third one ends on '[repository URI]/git-receive-pack' (for push) and '[repository // URL]/git-upload-pack' (for fetch). // The following checks will only be conducted for the second request, so we do not have to access the database too often. // The first request does not contain credentials and will thus already be blocked by the 'authenticateUser' method above. @@ -177,10 +177,10 @@ public void authenticateAndAuthorizeGitRequest(HttpServletRequest request, Repos return; } - LocalVCRepositoryUrl localVCRepositoryUrl = new LocalVCRepositoryUrl(request.getRequestURL().toString().replace("/info/refs", ""), localVCBaseUrl); + LocalVCRepositoryUri localVCRepositoryUri = new LocalVCRepositoryUri(request.getRequestURL().toString().replace("/info/refs", ""), localVCBaseUrl); - String projectKey = localVCRepositoryUrl.getProjectKey(); - String repositoryTypeOrUserName = localVCRepositoryUrl.getRepositoryTypeOrUserName(); + String projectKey = localVCRepositoryUri.getProjectKey(); + String repositoryTypeOrUserName = localVCRepositoryUri.getRepositoryTypeOrUserName(); ProgrammingExercise exercise; @@ -196,9 +196,9 @@ public void authenticateAndAuthorizeGitRequest(HttpServletRequest request, Repos throw new LocalVCForbiddenException(); } - authorizeUser(repositoryTypeOrUserName, user, exercise, repositoryAction, localVCRepositoryUrl.isPracticeRepository()); + authorizeUser(repositoryTypeOrUserName, user, exercise, repositoryAction, localVCRepositoryUri.isPracticeRepository()); - log.info("Authorizing user {} for repository {} took {}", user.getLogin(), localVCRepositoryUrl, TimeLogUtil.formatDurationFrom(timeNanoStart)); + log.info("Authorizing user {} for repository {} took {}", user.getLogin(), localVCRepositoryUri, TimeLogUtil.formatDurationFrom(timeNanoStart)); } private User authenticateUser(String authorizationHeader) throws LocalVCAuthException { @@ -280,10 +280,10 @@ private void authorizeUser(String repositoryTypeOrUserName, User user, Programmi * Returns the HTTP status code for the given exception thrown by the above method "authenticateAndAuthorizeGitRequest". * * @param exception The exception thrown. - * @param repositoryUrl The URL of the repository that was accessed. + * @param repositoryUri The URL of the repository that was accessed. * @return The HTTP status code. */ - public int getHttpStatusForException(Exception exception, String repositoryUrl) { + public int getHttpStatusForException(Exception exception, String repositoryUri) { if (exception instanceof LocalVCAuthException) { return HttpStatus.UNAUTHORIZED.value(); } @@ -291,7 +291,7 @@ else if (exception instanceof LocalVCForbiddenException) { return HttpStatus.FORBIDDEN.value(); } else { - log.error("Internal server error while trying to access repository {}: {}", repositoryUrl, exception.getMessage()); + log.error("Internal server error while trying to access repository {}: {}", repositoryUri, exception.getMessage()); return HttpStatus.INTERNAL_SERVER_ERROR.value(); } } @@ -309,14 +309,14 @@ public void processNewPush(String commitHash, Repository repository) { Path repositoryFolderPath = repository.getDirectory().toPath(); - LocalVCRepositoryUrl localVCRepositoryUrl = getLocalVCRepositoryUrl(repositoryFolderPath); + LocalVCRepositoryUri localVCRepositoryUri = getLocalVCRepositoryUri(repositoryFolderPath); - String repositoryTypeOrUserName = localVCRepositoryUrl.getRepositoryTypeOrUserName(); - String projectKey = localVCRepositoryUrl.getProjectKey(); + String repositoryTypeOrUserName = localVCRepositoryUri.getRepositoryTypeOrUserName(); + String projectKey = localVCRepositoryUri.getProjectKey(); ProgrammingExercise exercise = getProgrammingExercise(projectKey); - ProgrammingExerciseParticipation participation = getProgrammingExerciseParticipation(localVCRepositoryUrl, repositoryTypeOrUserName, exercise); + ProgrammingExerciseParticipation participation = getProgrammingExerciseParticipation(localVCRepositoryUri, repositoryTypeOrUserName, exercise); try { if (commitHash == null) { @@ -337,18 +337,18 @@ public void processNewPush(String commitHash, Repository repository) { // This catch clause does not catch exceptions that happen during runBuildJob() as that method is called asynchronously. // For exceptions happening inside runBuildJob(), the user is notified. See the addBuildJobToQueue() method in the LocalCIBuildJobManagementService for that. throw new VersionControlException( - "Could not process new push to repository " + localVCRepositoryUrl.getURI() + " and commit " + commitHash + ". No build job was queued.", e); + "Could not process new push to repository " + localVCRepositoryUri.getURI() + " and commit " + commitHash + ". No build job was queued.", e); } - log.info("New push processed to repository {} for commit {} in {}. A build job was queued.", localVCRepositoryUrl.getURI(), commitHash, + log.info("New push processed to repository {} for commit {} in {}. A build job was queued.", localVCRepositoryUri.getURI(), commitHash, TimeLogUtil.formatDurationFrom(timeNanoStart)); } - private ProgrammingExerciseParticipation getProgrammingExerciseParticipation(LocalVCRepositoryUrl localVCRepositoryUrl, String repositoryTypeOrUserName, + private ProgrammingExerciseParticipation getProgrammingExerciseParticipation(LocalVCRepositoryUri localVCRepositoryUri, String repositoryTypeOrUserName, ProgrammingExercise exercise) { ProgrammingExerciseParticipation participation; try { - participation = programmingExerciseParticipationService.getParticipationForRepository(exercise, repositoryTypeOrUserName, localVCRepositoryUrl.isPracticeRepository(), + participation = programmingExerciseParticipationService.getParticipationForRepository(exercise, repositoryTypeOrUserName, localVCRepositoryUri.isPracticeRepository(), true); } catch (EntityNotFoundException e) { @@ -368,13 +368,13 @@ private ProgrammingExercise getProgrammingExercise(String projectKey) { return exercise; } - private LocalVCRepositoryUrl getLocalVCRepositoryUrl(Path repositoryFolderPath) { + private LocalVCRepositoryUri getLocalVCRepositoryUri(Path repositoryFolderPath) { try { - return new LocalVCRepositoryUrl(repositoryFolderPath, localVCBaseUrl); + return new LocalVCRepositoryUri(repositoryFolderPath, localVCBaseUrl); } catch (LocalVCInternalException e) { // This means something is misconfigured. - throw new VersionControlException("Could not create valid repository URL from path " + repositoryFolderPath, e); + throw new VersionControlException("Could not create valid repository URI from path " + repositoryFolderPath, e); } } diff --git a/src/main/java/de/tum/in/www1/artemis/service/connectors/vcs/AbstractVersionControlService.java b/src/main/java/de/tum/in/www1/artemis/service/connectors/vcs/AbstractVersionControlService.java index 8523a53018e6..8df3826e349c 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/connectors/vcs/AbstractVersionControlService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/connectors/vcs/AbstractVersionControlService.java @@ -11,11 +11,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.ApplicationContext; import de.tum.in.www1.artemis.domain.ProgrammingExercise; import de.tum.in.www1.artemis.domain.Repository; -import de.tum.in.www1.artemis.domain.VcsRepositoryUrl; +import de.tum.in.www1.artemis.domain.VcsRepositoryUri; import de.tum.in.www1.artemis.domain.enumeration.InitializationState; import de.tum.in.www1.artemis.domain.participation.ProgrammingExerciseParticipation; import de.tum.in.www1.artemis.domain.participation.ProgrammingExerciseStudentParticipation; @@ -23,9 +22,8 @@ import de.tum.in.www1.artemis.repository.ProgrammingExerciseRepository; import de.tum.in.www1.artemis.repository.ProgrammingExerciseStudentParticipationRepository; import de.tum.in.www1.artemis.repository.TemplateProgrammingExerciseParticipationRepository; -import de.tum.in.www1.artemis.service.UrlService; +import de.tum.in.www1.artemis.service.UriService; import de.tum.in.www1.artemis.service.connectors.GitService; -import de.tum.in.www1.artemis.service.connectors.ci.ContinuousIntegrationService; public abstract class AbstractVersionControlService implements VersionControlService { @@ -37,11 +35,9 @@ public abstract class AbstractVersionControlService implements VersionControlSer @Value("${artemis.version-control.default-branch:main}") protected String defaultBranch; - private final ApplicationContext applicationContext; - protected final GitService gitService; - protected final UrlService urlService; + protected final UriService uriService; protected final ProgrammingExerciseStudentParticipationRepository studentParticipationRepository; @@ -49,12 +45,10 @@ public abstract class AbstractVersionControlService implements VersionControlSer protected final TemplateProgrammingExerciseParticipationRepository templateProgrammingExerciseParticipationRepository; - public AbstractVersionControlService(ApplicationContext applicationContext, GitService gitService, UrlService urlService, - ProgrammingExerciseStudentParticipationRepository studentParticipationRepository, ProgrammingExerciseRepository programmingExerciseRepository, - TemplateProgrammingExerciseParticipationRepository templateProgrammingExerciseParticipationRepository) { - this.applicationContext = applicationContext; + public AbstractVersionControlService(GitService gitService, UriService uriService, ProgrammingExerciseStudentParticipationRepository studentParticipationRepository, + ProgrammingExerciseRepository programmingExerciseRepository, TemplateProgrammingExerciseParticipationRepository templateProgrammingExerciseParticipationRepository) { this.gitService = gitService; - this.urlService = urlService; + this.uriService = uriService; this.studentParticipationRepository = studentParticipationRepository; this.programmingExerciseRepository = programmingExerciseRepository; this.templateProgrammingExerciseParticipationRepository = templateProgrammingExerciseParticipationRepository; @@ -63,26 +57,21 @@ public AbstractVersionControlService(ApplicationContext applicationContext, GitS /** * Adds a webhook for the specified repository to the given notification URL. * - * @param repositoryUrl The repository to which the webhook should get added to + * @param repositoryUri The repository to which the webhook should get added to * @param notificationUrl The URL the hook should notify * @param webHookName Any arbitrary name for the webhook */ - protected abstract void addWebHook(VcsRepositoryUrl repositoryUrl, String notificationUrl, String webHookName); + protected abstract void addWebHook(VcsRepositoryUri repositoryUri, String notificationUrl, String webHookName); /** * Adds an authenticated webhook for the specified repository to the given notification URL. * - * @param repositoryUrl The repository to which the webhook should get added to + * @param repositoryUri The repository to which the webhook should get added to * @param notificationUrl The URL the hook should notify * @param webHookName Any arbitrary name for the webhook * @param secretToken A secret token that authenticates the webhook against the system behind the notification URL */ - protected abstract void addAuthenticatedWebHook(VcsRepositoryUrl repositoryUrl, String notificationUrl, String webHookName, String secretToken); - - protected ContinuousIntegrationService getContinuousIntegrationService() { - // We need to get the CI service from the context, because Bamboo and Bitbucket would end up in a circular dependency otherwise - return applicationContext.getBean(ContinuousIntegrationService.class); - } + protected abstract void addAuthenticatedWebHook(VcsRepositoryUri repositoryUri, String notificationUrl, String webHookName, String secretToken); @Override public void addWebHooksForExercise(ProgrammingExercise exercise) { @@ -91,46 +80,46 @@ public void addWebHooksForExercise(ProgrammingExercise exercise) { final var artemisTestsHookPath = ARTEMIS_SERVER_URL + "/api/public/programming-exercises/test-cases-changed/" + exercise.getId(); // first add web hooks from the version control service to Artemis, so that Artemis is notified and can create ProgrammingSubmission when instructors push their template or // solution code - addWebHook(exercise.getVcsTemplateRepositoryUrl(), artemisTemplateHookPath, "Artemis WebHook"); - addWebHook(exercise.getVcsSolutionRepositoryUrl(), artemisSolutionHookPath, "Artemis WebHook"); - addWebHook(exercise.getVcsTestRepositoryUrl(), artemisTestsHookPath, "Artemis WebHook"); + addWebHook(exercise.getVcsTemplateRepositoryUri(), artemisTemplateHookPath, "Artemis WebHook"); + addWebHook(exercise.getVcsSolutionRepositoryUri(), artemisSolutionHookPath, "Artemis WebHook"); + addWebHook(exercise.getVcsTestRepositoryUri(), artemisTestsHookPath, "Artemis WebHook"); } @Override public void addWebHookForParticipation(ProgrammingExerciseParticipation participation) { if (!participation.getInitializationState().hasCompletedState(InitializationState.INITIALIZED)) { // first add a web hook from the version control service to Artemis, so that Artemis is notified can create a ProgrammingSubmission when students push their code - addWebHook(participation.getVcsRepositoryUrl(), ARTEMIS_SERVER_URL + "/api/public/programming-submissions/" + participation.getId(), "Artemis WebHook"); + addWebHook(participation.getVcsRepositoryUri(), ARTEMIS_SERVER_URL + "/api/public/programming-submissions/" + participation.getId(), "Artemis WebHook"); } } @Override - public VcsRepositoryUrl copyRepository(String sourceProjectKey, String sourceRepositoryName, String sourceBranch, String targetProjectKey, String targetRepositoryName) + public VcsRepositoryUri copyRepository(String sourceProjectKey, String sourceRepositoryName, String sourceBranch, String targetProjectKey, String targetRepositoryName) throws VersionControlException { sourceRepositoryName = sourceRepositoryName.toLowerCase(); targetRepositoryName = targetRepositoryName.toLowerCase(); final String targetRepoSlug = targetProjectKey.toLowerCase() + "-" + targetRepositoryName; // get the remote url of the source repo - final var sourceRepoUrl = getCloneRepositoryUrl(sourceProjectKey, sourceRepositoryName); + final var sourceRepoUri = getCloneRepositoryUri(sourceProjectKey, sourceRepositoryName); // get the remote url of the target repo - final var targetRepoUrl = getCloneRepositoryUrl(targetProjectKey, targetRepoSlug); + final var targetRepoUri = getCloneRepositoryUri(targetProjectKey, targetRepoSlug); Repository targetRepo = null; try { // create the new target repo createRepository(targetProjectKey, targetRepoSlug, null); // clone the source repo to the target directory - targetRepo = gitService.getOrCheckoutRepositoryIntoTargetDirectory(sourceRepoUrl, targetRepoUrl, true); + targetRepo = gitService.getOrCheckoutRepositoryIntoTargetDirectory(sourceRepoUri, targetRepoUri, true); // copy by pushing the source's content to the target's repo - gitService.pushSourceToTargetRepo(targetRepo, targetRepoUrl, sourceBranch); + gitService.pushSourceToTargetRepo(targetRepo, targetRepoUri, sourceBranch); } catch (GitAPIException | VersionControlException ex) { if (isReadFullyShortReadOfBlockException(ex)) { // NOTE: we ignore this particular error: it sometimes happens when pushing code that includes binary files, however the push operation typically worked correctly // TODO: verify that the push operation actually worked correctly, e.g. by comparing the number of commits in the source and target repo - log.warn("TransportException/EOFException with 'Short read of block' when copying repository {} to {}. Will ignore it", sourceRepoUrl, targetRepoUrl); - return targetRepoUrl; + log.warn("TransportException/EOFException with 'Short read of block' when copying repository {} to {}. Will ignore it", sourceRepoUri, targetRepoUri); + return targetRepoUri; } - Path localPath = gitService.getDefaultLocalPathOfRepo(targetRepoUrl); + Path localPath = gitService.getDefaultLocalPathOfRepo(targetRepoUri); // clean up in case of an error try { if (targetRepo != null) { @@ -149,7 +138,7 @@ public VcsRepositoryUrl copyRepository(String sourceProjectKey, String sourceRep throw new VersionControlException("Could not copy repository " + sourceRepositoryName + " to the target repository " + targetRepositoryName, ex); } - return targetRepoUrl; + return targetRepoUri; } /** @@ -179,7 +168,7 @@ public String getOrRetrieveBranchOfParticipation(ProgrammingExerciseParticipatio @Override public String getOrRetrieveBranchOfStudentParticipation(ProgrammingExerciseStudentParticipation participation) { if (participation.getBranch() == null) { - String branch = getDefaultBranchOfRepository(participation.getVcsRepositoryUrl()); + String branch = getDefaultBranchOfRepository(participation.getVcsRepositoryUri()); participation.setBranch(branch); studentParticipationRepository.save(participation); } @@ -193,7 +182,7 @@ public String getOrRetrieveBranchOfExercise(ProgrammingExercise programmingExerc if (!Hibernate.isInitialized(programmingExercise.getTemplateParticipation())) { programmingExercise.setTemplateParticipation(templateProgrammingExerciseParticipationRepository.findByProgrammingExerciseIdElseThrow(programmingExercise.getId())); } - String branch = getDefaultBranchOfRepository(programmingExercise.getVcsTemplateRepositoryUrl()); + String branch = getDefaultBranchOfRepository(programmingExercise.getVcsTemplateRepositoryUri()); programmingExercise.setBranch(branch); programmingExerciseRepository.save(programmingExercise); } diff --git a/src/main/java/de/tum/in/www1/artemis/service/connectors/vcs/VersionControlService.java b/src/main/java/de/tum/in/www1/artemis/service/connectors/vcs/VersionControlService.java index ced6392a9c93..d2253e17bc69 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/connectors/vcs/VersionControlService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/connectors/vcs/VersionControlService.java @@ -45,11 +45,11 @@ public interface VersionControlService { void deleteProject(String projectKey); /** - * Deletes the repository at the given url + * Deletes the repository at the given uri * - * @param repositoryUrl of the repository that should be deleted + * @param repositoryUri of the repository that should be deleted */ - void deleteRepository(VcsRepositoryUrl repositoryUrl); + void deleteRepository(VcsRepositoryUri repositoryUri); /** * Get the clone URL used for cloning @@ -58,15 +58,15 @@ public interface VersionControlService { * @param repositorySlug The repository slug * @return The clone URL */ - VcsRepositoryUrl getCloneRepositoryUrl(String projectKey, String repositorySlug); + VcsRepositoryUri getCloneRepositoryUri(String projectKey, String repositorySlug); /** - * Check if the given repository url is valid and accessible. + * Check if the given repository uri is valid and accessible. * - * @param repositoryUrl the VCS repository URL + * @param repositoryUri the VCS repository URI * @return whether the repository is valid */ - Boolean repositoryUrlIsValid(@Nullable VcsRepositoryUrl repositoryUrl); + Boolean repositoryUriIsValid(@Nullable VcsRepositoryUri repositoryUri); /** * Get the last commit details that are included in the given requestBody that notifies about a push @@ -125,43 +125,43 @@ public interface VersionControlService { * @return The URL for cloning the repository * @throws VersionControlException if the repository could not be copied on the VCS server (e.g. because the source repo does not exist) */ - VcsRepositoryUrl copyRepository(String sourceProjectKey, String sourceRepositoryName, String sourceBranch, String targetProjectKey, String targetRepositoryName) + VcsRepositoryUri copyRepository(String sourceProjectKey, String sourceRepositoryName, String sourceBranch, String targetProjectKey, String targetRepositoryName) throws VersionControlException; /** * Add the user to the repository * - * @param repositoryUrl The repository url of the repository to which to add the user. It contains the project key & the repository name. + * @param repositoryUri The repository uri of the repository to which to add the user. It contains the project key & the repository name. * @param user User which to add to the repository * @param permissions The permissions the user should get for the repository. */ - void addMemberToRepository(VcsRepositoryUrl repositoryUrl, User user, VersionControlRepositoryPermission permissions); + void addMemberToRepository(VcsRepositoryUri repositoryUri, User user, VersionControlRepositoryPermission permissions); /** * Remove the user from the repository * - * @param repositoryUrl The repository url of the repository from which to remove the user. It contains the project key & the repository name. + * @param repositoryUri The repository uri of the repository from which to remove the user. It contains the project key & the repository name. * @param user User which to remove from the repository */ - void removeMemberFromRepository(VcsRepositoryUrl repositoryUrl, User user); + void removeMemberFromRepository(VcsRepositoryUri repositoryUri, User user); /** * Removes the user's write permissions for a repository. * - * @param repositoryUrl The repository url of the repository to update. It contains the project key & the repository name. + * @param repositoryUri The repository uri of the repository to update. It contains the project key & the repository name. * @param projectKey The projectKey that the repo is part of in the VCS. * @param users Set of users for which to change permissions * @throws VersionControlException If the communication with the VCS fails. */ - void setRepositoryPermissionsToReadOnly(VcsRepositoryUrl repositoryUrl, String projectKey, Set users) throws VersionControlException; + void setRepositoryPermissionsToReadOnly(VcsRepositoryUri repositoryUri, String projectKey, Set users) throws VersionControlException; /** * Get the default branch of the repository * - * @param repositoryUrl The repository url to get the default branch for. + * @param repositoryUri The repository uri to get the default branch for. * @return the name of the default branch, e.g. 'main' */ - String getDefaultBranchOfRepository(VcsRepositoryUrl repositoryUrl) throws VersionControlException; + String getDefaultBranchOfRepository(VcsRepositoryUri repositoryUri) throws VersionControlException; /** * Get the default branch of the repository @@ -171,17 +171,17 @@ VcsRepositoryUrl copyRepository(String sourceProjectKey, String sourceRepository * @return the name of the default branch, e.g. 'main' */ default String getDefaultBranchOfRepository(String projectKey, String repositorySlug) throws VersionControlException { - return getDefaultBranchOfRepository(getCloneRepositoryUrl(projectKey, repositorySlug)); + return getDefaultBranchOfRepository(getCloneRepositoryUri(projectKey, repositorySlug)); } /** * Unprotects a branch from the repository, so that the history can be changed (important for combine template commits). * - * @param repositoryUrl The repository url of the repository to update. It contains the project key & the repository name. + * @param repositoryUri The repository uri of the repository to update. It contains the project key & the repository name. * @param branch The name of the branch to unprotect (e.g "main") * @throws VersionControlException If the communication with the VCS fails. */ - void unprotectBranch(VcsRepositoryUrl repositoryUrl, String branch) throws VersionControlException; + void unprotectBranch(VcsRepositoryUri repositoryUri, String branch) throws VersionControlException; /** * Checks if the underlying VCS server is up and running and gives some additional information about the running diff --git a/src/main/java/de/tum/in/www1/artemis/service/dto/athena/ProgrammingExerciseDTO.java b/src/main/java/de/tum/in/www1/artemis/service/dto/athena/ProgrammingExerciseDTO.java index 2792226f8839..20729a8acc17 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/dto/athena/ProgrammingExerciseDTO.java +++ b/src/main/java/de/tum/in/www1/artemis/service/dto/athena/ProgrammingExerciseDTO.java @@ -13,7 +13,7 @@ * A DTO representing a ProgrammingExercise, for transferring data to Athena */ public record ProgrammingExerciseDTO(long id, String title, double maxPoints, double bonusPoints, String gradingInstructions, List gradingCriteria, - String problemStatement, String programmingLanguage, String solutionRepositoryUrl, String templateRepositoryUrl, String testsRepositoryUrl) implements ExerciseDTO { + String problemStatement, String programmingLanguage, String solutionRepositoryUri, String templateRepositoryUri, String testsRepositoryUri) implements ExerciseDTO { /** * Create a new TextExerciseDTO from a TextExercise diff --git a/src/main/java/de/tum/in/www1/artemis/service/dto/athena/ProgrammingSubmissionDTO.java b/src/main/java/de/tum/in/www1/artemis/service/dto/athena/ProgrammingSubmissionDTO.java index e5045067f23e..19eede1641e2 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/dto/athena/ProgrammingSubmissionDTO.java +++ b/src/main/java/de/tum/in/www1/artemis/service/dto/athena/ProgrammingSubmissionDTO.java @@ -9,7 +9,7 @@ /** * A DTO representing a ProgrammingSubmission, for transferring data to Athena */ -public record ProgrammingSubmissionDTO(long id, long exerciseId, String repositoryUrl) implements SubmissionDTO { +public record ProgrammingSubmissionDTO(long id, long exerciseId, String repositoryUri) implements SubmissionDTO { /** * Creates a new ProgrammingSubmissionDTO from a ProgrammingSubmission. The DTO also contains the exerciseId of the exercise the submission belongs to. diff --git a/src/main/java/de/tum/in/www1/artemis/service/exam/ExamService.java b/src/main/java/de/tum/in/www1/artemis/service/exam/ExamService.java index 11e97c99ed93..60c6d532e926 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/exam/ExamService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/exam/ExamService.java @@ -562,7 +562,7 @@ public void filterParticipationForExercise(StudentExam studentExam, Exercise exe } if (exercise instanceof ProgrammingExercise programmingExercise) { - programmingExercise.setTestRepositoryUrl(null); + programmingExercise.setTestRepositoryUri(null); } // get user's participation for the exercise @@ -1321,7 +1321,7 @@ public void combineTemplateCommitsOfAllProgrammingExercisesInExam(Exam exam) { try { ProgrammingExercise programmingExerciseWithTemplateParticipation = programmingExerciseRepository .findByIdWithTemplateAndSolutionParticipationElseThrow(exercise.getId()); - gitService.combineAllCommitsOfRepositoryIntoOne(programmingExerciseWithTemplateParticipation.getTemplateParticipation().getVcsRepositoryUrl()); + gitService.combineAllCommitsOfRepositoryIntoOne(programmingExerciseWithTemplateParticipation.getTemplateParticipation().getVcsRepositoryUri()); log.debug("Finished combination of template commits for programming exercise {}", programmingExerciseWithTemplateParticipation); } catch (GitAPIException e) { diff --git a/src/main/java/de/tum/in/www1/artemis/service/export/DataExportExamCreationService.java b/src/main/java/de/tum/in/www1/artemis/service/export/DataExportExamCreationService.java index ab6078131f47..f88deb076a91 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/export/DataExportExamCreationService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/export/DataExportExamCreationService.java @@ -116,7 +116,7 @@ private void addExamScores(StudentExam studentExam, Path examWorkingDir) throws var gradingScale = gradingScaleRepository.findByExamId(studentExam.getExam().getId()); List headers = new ArrayList<>(); var examResults = getExamResultsStreamToPrint(studentResult, headers, gradingScale); - CSVFormat csvFormat = CSVFormat.DEFAULT.builder().setHeader(headers.toArray(new String[0])).build(); + CSVFormat csvFormat = CSVFormat.DEFAULT.builder().setHeader(headers.toArray(String[]::new)).build(); try (final CSVPrinter printer = new CSVPrinter( Files.newBufferedWriter(examWorkingDir.resolve(EXAM_DIRECTORY_PREFIX + studentExam.getId() + "_result" + CSV_FILE_EXTENSION)), csvFormat)) { printer.printRecord(examResults); @@ -169,7 +169,7 @@ private Stream getExamResultsStreamToPrint(ExamScoresDTO.StudentResult studen private void addGeneralExamInformation(StudentExam studentExam, Path examWorkingDir) throws IOException { List headers = new ArrayList<>(); var generalExamInformation = getGeneralExamInformationStreamToPrint(studentExam, headers); - CSVFormat csvFormat = CSVFormat.DEFAULT.builder().setHeader(headers.toArray(new String[0])).build(); + CSVFormat csvFormat = CSVFormat.DEFAULT.builder().setHeader(headers.toArray(String[]::new)).build(); try (CSVPrinter printer = new CSVPrinter(Files.newBufferedWriter(examWorkingDir.resolve(EXAM_DIRECTORY_PREFIX + studentExam.getId() + CSV_FILE_EXTENSION)), csvFormat)) { printer.printRecord(generalExamInformation); diff --git a/src/main/java/de/tum/in/www1/artemis/service/export/DataExportExerciseCreationService.java b/src/main/java/de/tum/in/www1/artemis/service/export/DataExportExerciseCreationService.java index 54f106ba5c6c..b7263df54c94 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/export/DataExportExerciseCreationService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/export/DataExportExerciseCreationService.java @@ -375,7 +375,7 @@ private void addComplaintData(Complaint complaint, Path outputDir) throws IOExce headers.add("accepted"); dataStreamBuilder.add(complaint.isAccepted()); } - CSVFormat csvFormat = CSVFormat.DEFAULT.builder().setHeader(headers.toArray(new String[0])).build(); + CSVFormat csvFormat = CSVFormat.DEFAULT.builder().setHeader(headers.toArray(String[]::new)).build(); var prefix = complaint.getComplaintType() == ComplaintType.COMPLAINT ? "complaint_" : "more_feedback_"; try (final var printer = new CSVPrinter(Files.newBufferedWriter(outputDir.resolve(prefix + complaint.getId() + CSV_FILE_EXTENSION)), csvFormat)) { @@ -420,7 +420,7 @@ private void createPlagiarismCaseInfoExport(Exercise exercise, Path exercisePath else if (plagiarismCase.getVerdict() == PlagiarismVerdict.WARNING) { dataStreamBuilder.add(plagiarismCase.getVerdictMessage()); } - CSVFormat csvFormat = CSVFormat.DEFAULT.builder().setHeader(headers.toArray(new String[0])).build(); + CSVFormat csvFormat = CSVFormat.DEFAULT.builder().setHeader(headers.toArray(String[]::new)).build(); try (final CSVPrinter printer = new CSVPrinter(Files.newBufferedWriter(exercisePath.resolve("plagiarism_case_" + plagiarismCase.getId() + CSV_FILE_EXTENSION)), csvFormat)) { diff --git a/src/main/java/de/tum/in/www1/artemis/service/export/ProgrammingExerciseExportService.java b/src/main/java/de/tum/in/www1/artemis/service/export/ProgrammingExerciseExportService.java index 519bb93f6590..105f9dff783a 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/export/ProgrammingExerciseExportService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/export/ProgrammingExerciseExportService.java @@ -359,8 +359,8 @@ public Optional exportInstructorRepositoryForExercise(long exerciseId, Rep } var exercise = exerciseOrEmpty.get(); String zippedRepoName = getZippedRepoName(exercise, repositoryType.getName()); - var repositoryUrl = exercise.getRepositoryURL(repositoryType); - return exportRepository(repositoryUrl, repositoryType.getName(), zippedRepoName, exercise, workingDir, outputDir, null, exportErrors); + var repositoryUri = exercise.getRepositoryURL(repositoryType); + return exportRepository(repositoryUri, repositoryType.getName(), zippedRepoName, exercise, workingDir, outputDir, null, exportErrors); } /** @@ -381,8 +381,8 @@ public Optional exportInstructorAuxiliaryRepositoryForExercise(long exerci } var exercise = exerciseOrEmpty.get(); String zippedRepoName = getZippedRepoName(exercise, auxiliaryRepository.getRepositoryName()); - var repositoryUrl = auxiliaryRepository.getVcsRepositoryUrl(); - return exportRepository(repositoryUrl, auxiliaryRepository.getName(), zippedRepoName, exercise, workingDir, outputDir, null, exportErrors); + var repositoryUri = auxiliaryRepository.getVcsRepositoryUri(); + return exportRepository(repositoryUri, auxiliaryRepository.getName(), zippedRepoName, exercise, workingDir, outputDir, null, exportErrors); } /** @@ -409,8 +409,8 @@ public Optional exportStudentRequestedRepository(long exerciseId, boolean return exportSolutionAndTestStudentRepositoryForExercise(zippedRepoName, exercise, uniquePath, gitDirFilter, exportErrors); } else { - var repositoryUrl = exercise.getRepositoryURL(repositoryType); - return exportRepository(repositoryUrl, repositoryType.getName(), zippedRepoName, exercise, uniquePath, uniquePath, gitDirFilter, exportErrors); + var repositoryUri = exercise.getRepositoryURL(repositoryType); + return exportRepository(repositoryUri, repositoryType.getName(), zippedRepoName, exercise, uniquePath, uniquePath, gitDirFilter, exportErrors); } } @@ -437,25 +437,25 @@ private String getZippedRepoName(ProgrammingExercise exercise, String repository /** * Exports a given repository and stores it in a zip file. * - * @param repositoryUrl the url of the repository + * @param repositoryUri the url of the repository * @param zippedRepoName the name of the zip file * @param workingDir the directory used to clone the repository * @param outputDir the directory used for store the zip file * @param contentFilter a filter for the content of the zip file * @return an optional containing the path to the zip file if the export was successful */ - private Optional exportRepository(VcsRepositoryUrl repositoryUrl, String repositoryName, String zippedRepoName, ProgrammingExercise exercise, Path workingDir, + private Optional exportRepository(VcsRepositoryUri repositoryUri, String repositoryName, String zippedRepoName, ProgrammingExercise exercise, Path workingDir, Path outputDir, @Nullable Predicate contentFilter, List exportErrors) { try { - // It's not guaranteed that the repository url is defined (old courses). - if (repositoryUrl == null) { - var error = "Failed to export instructor repository " + repositoryName + " because the repository url is not defined."; + // It's not guaranteed that the repository uri is defined (old courses). + if (repositoryUri == null) { + var error = "Failed to export instructor repository " + repositoryName + " because the repository uri is not defined."; log.error(error); exportErrors.add(error); return Optional.empty(); } - Path zippedRepo = createZipForRepository(repositoryUrl, zippedRepoName, workingDir, outputDir, contentFilter); + Path zippedRepo = createZipForRepository(repositoryUri, zippedRepoName, workingDir, outputDir, contentFilter); if (zippedRepo != null) { return Optional.of(zippedRepo.toFile()); } @@ -470,8 +470,8 @@ private Optional exportRepository(VcsRepositoryUrl repositoryUrl, String r private Optional exportSolutionAndTestStudentRepositoryForExercise(String zippedRepoName, ProgrammingExercise exercise, Path uniquePath, @Nullable Predicate contentFilter, List exportErrors) { - if (exercise.getVcsSolutionRepositoryUrl() == null || exercise.getVcsTestRepositoryUrl() == null) { - var error = "Failed to export repository of exercise " + exercise.getTitle() + " because the repository url is not defined."; + if (exercise.getVcsSolutionRepositoryUri() == null || exercise.getVcsTestRepositoryUri() == null) { + var error = "Failed to export repository of exercise " + exercise.getTitle() + " because the repository uri is not defined."; log.error(error); exportErrors.add(error); return Optional.empty(); @@ -481,16 +481,16 @@ private Optional exportSolutionAndTestStudentRepositoryForExercise(String Path zipPath = uniquePath.resolve("zip"); try { - gitService.getOrCheckoutRepository(exercise.getVcsTestRepositoryUrl(), clonePath, true); + gitService.getOrCheckoutRepository(exercise.getVcsTestRepositoryUri(), clonePath, true); if (!clonePath.toFile().exists()) { Files.createDirectories(clonePath); } String assignmentPath = RepositoryCheckoutPath.ASSIGNMENT.forProgrammingLanguage(exercise.getProgrammingLanguage()); FileUtils.deleteDirectory(clonePath.resolve(assignmentPath).toFile()); - gitService.getOrCheckoutRepository(exercise.getVcsSolutionRepositoryUrl(), clonePath.resolve(assignmentPath), true); + gitService.getOrCheckoutRepository(exercise.getVcsSolutionRepositoryUri(), clonePath.resolve(assignmentPath), true); for (AuxiliaryRepository auxRepo : exercise.getAuxiliaryRepositoriesForBuildPlan()) { FileUtils.deleteDirectory(clonePath.resolve(auxRepo.getCheckoutDirectory()).toFile()); - gitService.getOrCheckoutRepository(auxRepo.getVcsRepositoryUrl(), clonePath.resolve(auxRepo.getCheckoutDirectory()), true); + gitService.getOrCheckoutRepository(auxRepo.getVcsRepositoryUri(), clonePath.resolve(auxRepo.getCheckoutDirectory()), true); } return Optional.of(gitService.zipFiles(clonePath, zippedRepoName, zipPath.toString(), contentFilter).toFile()); @@ -583,7 +583,7 @@ public List exportStudentRepositories(ProgrammingExercise programmingExerc /** * Creates a zip file with the contents of the git repository. Note that the zip file is deleted in 5 minutes. * - * @param repositoryUrl The url of the repository to zip + * @param repositoryUri The url of the repository to zip * @param zipFilename The name of the zip file * @param outputDir The directory used to store the zip file * @param contentFilter The path filter to exclude some files, can be null to include everything @@ -591,13 +591,13 @@ public List exportStudentRepositories(ProgrammingExercise programmingExerc * @throws IOException if the zip file couldn't be created * @throws GitAPIException if the repo couldn't get checked out */ - private Path createZipForRepository(VcsRepositoryUrl repositoryUrl, String zipFilename, Path workingDir, Path outputDir, @Nullable Predicate contentFilter) + private Path createZipForRepository(VcsRepositoryUri repositoryUri, String zipFilename, Path workingDir, Path outputDir, @Nullable Predicate contentFilter) throws IOException, GitAPIException, GitException, UncheckedIOException { var repositoryDir = fileService.getTemporaryUniquePathWithoutPathCreation(workingDir, 5); Path localRepoPath; // Checkout the repository - try (Repository repository = gitService.getOrCheckoutRepository(repositoryUrl, repositoryDir, false)) { + try (Repository repository = gitService.getOrCheckoutRepository(repositoryUri, repositoryDir, false)) { gitService.resetToOriginHead(repository); localRepoPath = repository.getLocalPath(); } @@ -643,8 +643,8 @@ private File createZipWithAllRepositories(ProgrammingExercise programmingExercis */ public Path createZipForRepositoryWithParticipation(final ProgrammingExercise programmingExercise, final ProgrammingExerciseStudentParticipation participation, final RepositoryExportOptionsDTO repositoryExportOptions, Path workingDir, Path outputDir) throws IOException, UncheckedIOException { - if (participation.getVcsRepositoryUrl() == null) { - log.warn("Ignore participation {} for export, because its repository URL is null", participation.getId()); + if (participation.getVcsRepositoryUri() == null) { + log.warn("Ignore participation {} for export, because its repository URI is null", participation.getId()); return null; } diff --git a/src/main/java/de/tum/in/www1/artemis/service/hestia/ProgrammingExerciseGitDiffReportService.java b/src/main/java/de/tum/in/www1/artemis/service/hestia/ProgrammingExerciseGitDiffReportService.java index 1bf3c90949bc..8e38745bf93b 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/hestia/ProgrammingExerciseGitDiffReportService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/hestia/ProgrammingExerciseGitDiffReportService.java @@ -180,7 +180,7 @@ public ProgrammingExerciseGitDiffReport createReportForSubmissionWithTemplate(Pr var templateParticipation = templateProgrammingExerciseParticipationRepository.findByProgrammingExerciseId(exercise.getId()).orElseThrow(); Repository templateRepo = prepareTemplateRepository(templateParticipation); - var repo1 = gitService.checkoutRepositoryAtCommit(((ProgrammingExerciseParticipation) submission.getParticipation()).getVcsRepositoryUrl(), submission.getCommitHash(), + var repo1 = gitService.checkoutRepositoryAtCommit(((ProgrammingExerciseParticipation) submission.getParticipation()).getVcsRepositoryUri(), submission.getCommitHash(), false); var oldTreeParser = new FileTreeIterator(templateRepo); var newTreeParser = new FileTreeIterator(repo1); @@ -198,7 +198,7 @@ public ProgrammingExerciseGitDiffReport createReportForSubmissionWithTemplate(Pr * @param localPathRepoB local path to the checked out instance of the second repo to compare * @return cumulative number of lines in the git diff of given repositories */ - public int calculateNumberOfDiffLinesBetweenRepos(VcsRepositoryUrl urlRepoA, Path localPathRepoA, VcsRepositoryUrl urlRepoB, Path localPathRepoB) { + public int calculateNumberOfDiffLinesBetweenRepos(VcsRepositoryUri urlRepoA, Path localPathRepoA, VcsRepositoryUri urlRepoB, Path localPathRepoB) { var repoA = gitService.getExistingCheckedOutRepositoryByLocalPath(localPathRepoA, urlRepoA); var repoB = gitService.getExistingCheckedOutRepositoryByLocalPath(localPathRepoB, urlRepoB); @@ -228,7 +228,7 @@ public int calculateNumberOfDiffLinesBetweenRepos(VcsRepositoryUrl urlRepoA, Pat private ProgrammingExerciseGitDiffReport generateReport(TemplateProgrammingExerciseParticipation templateParticipation, SolutionProgrammingExerciseParticipation solutionParticipation) throws GitAPIException, IOException { Repository templateRepo = prepareTemplateRepository(templateParticipation); - var solutionRepo = gitService.getOrCheckoutRepository(solutionParticipation.getVcsRepositoryUrl(), true); + var solutionRepo = gitService.getOrCheckoutRepository(solutionParticipation.getVcsRepositoryUri(), true); gitService.resetToOriginHead(solutionRepo); gitService.pullIgnoreConflicts(solutionRepo); @@ -246,7 +246,7 @@ private ProgrammingExerciseGitDiffReport generateReport(TemplateProgrammingExerc * @throws GitAPIException If an error occurs while accessing the git repository */ private Repository prepareTemplateRepository(TemplateProgrammingExerciseParticipation templateParticipation) throws GitAPIException { - var templateRepo = gitService.getOrCheckoutRepository(templateParticipation.getVcsRepositoryUrl(), true); + var templateRepo = gitService.getOrCheckoutRepository(templateParticipation.getVcsRepositoryUri(), true); gitService.resetToOriginHead(templateRepo); gitService.pullIgnoreConflicts(templateRepo); return templateRepo; @@ -262,13 +262,13 @@ private Repository prepareTemplateRepository(TemplateProgrammingExerciseParticip * @throws IOException If an error occurs while accessing the file system */ public ProgrammingExerciseGitDiffReport generateReportForSubmissions(ProgrammingSubmission submission1, ProgrammingSubmission submission2) throws GitAPIException, IOException { - var repositoryUrl = ((ProgrammingExerciseParticipation) submission1.getParticipation()).getVcsRepositoryUrl(); - var repo1 = gitService.getOrCheckoutRepository(repositoryUrl, true); + var repositoryUri = ((ProgrammingExerciseParticipation) submission1.getParticipation()).getVcsRepositoryUri(); + var repo1 = gitService.getOrCheckoutRepository(repositoryUri, true); var repo1Path = repo1.getLocalPath(); var repo2Path = fileService.getTemporaryUniqueSubfolderPath(repo1Path.getParent(), 5); FileSystemUtils.copyRecursively(repo1Path, repo2Path); repo1 = gitService.checkoutRepositoryAtCommit(repo1, submission1.getCommitHash()); - var repo2 = gitService.getExistingCheckedOutRepositoryByLocalPath(repo2Path, repositoryUrl); + var repo2 = gitService.getExistingCheckedOutRepositoryByLocalPath(repo2Path, repositoryUri); repo2 = gitService.checkoutRepositoryAtCommit(repo2, submission2.getCommitHash()); return parseFilesAndCreateReport(repo1, repo2); } diff --git a/src/main/java/de/tum/in/www1/artemis/service/hestia/TestwiseCoverageService.java b/src/main/java/de/tum/in/www1/artemis/service/hestia/TestwiseCoverageService.java index 61ba073e54d8..cbe9088285de 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/hestia/TestwiseCoverageService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/hestia/TestwiseCoverageService.java @@ -211,7 +211,7 @@ public void createTestwiseCoverageReport(Map> fi private Map getLineCountByFilePath(ProgrammingSubmission submission) { try { var solutionParticipation = (SolutionProgrammingExerciseParticipation) submission.getParticipation(); - var solutionRepo = gitService.getOrCheckoutRepository(solutionParticipation.getVcsRepositoryUrl(), true); + var solutionRepo = gitService.getOrCheckoutRepository(solutionParticipation.getVcsRepositoryUri(), true); gitService.resetToOriginHead(solutionRepo); gitService.pullIgnoreConflicts(solutionRepo); var solutionFiles = repositoryService.getFilesWithContent(solutionRepo); @@ -255,8 +255,8 @@ private double calculateAggregatedLineCoverage(Map lineCountByF * the same lines, but referencing a different test case. This mapping is still required, but simple summing may * count the same covered lines multiple times. * - * @param report the report for which the line counts of its file reports should be caluclated and saved - * @return the number of covered lines by file path + * @param report the report for which the line counts of its file reports should be calculated and saved + * @return a map with the number of covered lines (value) by file path (key) */ private Map calculateAndSaveUniqueLineCountsByFilePath(CoverageReport report) { var coveredLinesByFilePath = new HashMap(); @@ -272,10 +272,10 @@ private Map calculateAndSaveUniqueLineCountsByFilePath(Coverage } /** - * Return the testwise coverage report for the latest solution submission for a programming exercise without the file reports. + * Return the test-wise coverage report for the latest solution submission for a programming exercise without the file reports. * * @param programmingExercise the exercise for which the latest coverage report should be retrieved - * @return an Optional of the testwise coverage report for the latest solution submission without the file reports + * @return an Optional of the test-wise coverage report for the latest solution submission without the file reports * if a report exists for the latest submission, otherwise an empty Optional */ public Optional getCoverageReportForLatestSolutionSubmissionFromProgrammingExercise(ProgrammingExercise programmingExercise) { @@ -287,10 +287,10 @@ public Optional getCoverageReportForLatestSolutionSubmissionFrom } /** - * Return the full testwise coverage report for the latest solution submission for a programming exercise containing all file reports + * Return the full test-wise coverage report for the latest solution submission for a programming exercise containing all file reports * * @param programmingExercise the exercise for which the latest coverage report should be retrieved - * @return an Optional of the full testwise coverage report for the latest solution submission with all file reports + * @return an Optional of the full test-wise coverage report for the latest solution submission with all file reports * if a report exists for the latest submission, otherwise an empty Optional */ public Optional getFullCoverageReportForLatestSolutionSubmissionFromProgrammingExercise(ProgrammingExercise programmingExercise) { diff --git a/src/main/java/de/tum/in/www1/artemis/service/hestia/behavioral/BehavioralBlackboard.java b/src/main/java/de/tum/in/www1/artemis/service/hestia/behavioral/BehavioralBlackboard.java index bd1ef3c557c4..13e95d073494 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/hestia/behavioral/BehavioralBlackboard.java +++ b/src/main/java/de/tum/in/www1/artemis/service/hestia/behavioral/BehavioralBlackboard.java @@ -7,7 +7,7 @@ import de.tum.in.www1.artemis.domain.hestia.ProgrammingExerciseSolutionEntry; /** - * The blackboard for creating SolutionEntries for behavioral test cases utilizing the git-diff and teswise coverage report. + * The blackboard for creating SolutionEntries for behavioral test cases utilizing the git-diff and test-wise coverage report. */ public class BehavioralBlackboard { diff --git a/src/main/java/de/tum/in/www1/artemis/service/hestia/behavioral/BehavioralTestCaseService.java b/src/main/java/de/tum/in/www1/artemis/service/hestia/behavioral/BehavioralTestCaseService.java index 275c33d4aaa6..9cad795bd8a0 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/hestia/behavioral/BehavioralTestCaseService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/hestia/behavioral/BehavioralTestCaseService.java @@ -169,7 +169,7 @@ private Map readSolutionRepo(ProgrammingExercise programmingExer return Collections.emptyMap(); } var solutionParticipation = solutionParticipationOptional.get(); - var solutionRepo = gitService.getOrCheckoutRepository(solutionParticipation.getVcsRepositoryUrl(), true); + var solutionRepo = gitService.getOrCheckoutRepository(solutionParticipation.getVcsRepositoryUri(), true); gitService.resetToOriginHead(solutionRepo); gitService.pullIgnoreConflicts(solutionRepo); diff --git a/src/main/java/de/tum/in/www1/artemis/service/hestia/behavioral/GroupedFile.java b/src/main/java/de/tum/in/www1/artemis/service/hestia/behavioral/GroupedFile.java index a749a3e96f76..3b0ab9a170e0 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/hestia/behavioral/GroupedFile.java +++ b/src/main/java/de/tum/in/www1/artemis/service/hestia/behavioral/GroupedFile.java @@ -118,63 +118,24 @@ public boolean equals(Object obj) { && Objects.equals(commonChanges, that.commonChanges); } - public static class ChangeBlock implements Comparable { - - private SortedSet lines; - - private boolean isPotential; + public record ChangeBlock(SortedSet lines, boolean isPotential) implements Comparable { public ChangeBlock(Collection lines) { - this.lines = new TreeSet<>(lines); - this.isPotential = false; + this(new TreeSet<>(lines), false); } public ChangeBlock(Collection lines, boolean isPotential) { - this.lines = new TreeSet<>(lines); - this.isPotential = isPotential; - } - - public boolean intersectsOrTouches(GroupedFile.ChangeBlock other) { - return (this.lines.first() > other.lines.first() && this.lines.first() <= other.lines.last() + 1) - || (this.lines.first() < other.lines.first() && this.lines.last() >= other.lines.first() - 1); + this(new TreeSet<>(lines), isPotential); } @Override public int compareTo(GroupedFile.ChangeBlock other) { - return this.lines.first().compareTo(other.lines.first()); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - ChangeBlock that = (ChangeBlock) obj; - return isPotential == that.isPotential && lines.equals(that.lines); - } - - @Override - public int hashCode() { - return Objects.hash(lines, isPotential); - } - - public SortedSet getLines() { - return lines; - } - - public void setLines(Collection lines) { - this.lines = new TreeSet<>(lines); - } - - public boolean isPotential() { - return isPotential; + return Integer.compare(this.lines.first(), other.lines.first()); } - public void setPotential(boolean potential) { - isPotential = potential; + public boolean intersectsOrTouches(ChangeBlock other) { + return (this.lines.first() > other.lines.first() && this.lines.first() <= other.lines.last() + 1) + || (this.lines.first() < other.lines.first() && this.lines.last() >= other.lines.first() - 1); } } } diff --git a/src/main/java/de/tum/in/www1/artemis/service/hestia/behavioral/knowledgesource/AddUncoveredLinesAsPotentialCodeBlocks.java b/src/main/java/de/tum/in/www1/artemis/service/hestia/behavioral/knowledgesource/AddUncoveredLinesAsPotentialCodeBlocks.java index 25fe433da41f..531639d227f8 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/hestia/behavioral/knowledgesource/AddUncoveredLinesAsPotentialCodeBlocks.java +++ b/src/main/java/de/tum/in/www1/artemis/service/hestia/behavioral/knowledgesource/AddUncoveredLinesAsPotentialCodeBlocks.java @@ -41,12 +41,12 @@ public boolean executeAction() { var newChangeBlocks = new TreeSet(); for (ChangeBlock commonChange : groupedFile.getCommonChanges()) { - var firstLine = commonChange.getLines().first(); + var firstLine = commonChange.lines().first(); var potentialPrefix = getPotentialPrefix(firstLine, groupedFile.getFileContent()); if (potentialPrefix != null) { newChangeBlocks.add(potentialPrefix); } - var lastLine = commonChange.getLines().last(); + var lastLine = commonChange.lines().last(); var potentialPostfix = getPotentialPostfix(lastLine, groupedFile.getFileContent()); if (potentialPostfix != null) { newChangeBlocks.add(potentialPostfix); diff --git a/src/main/java/de/tum/in/www1/artemis/service/hestia/behavioral/knowledgesource/CombineChangeBlocks.java b/src/main/java/de/tum/in/www1/artemis/service/hestia/behavioral/knowledgesource/CombineChangeBlocks.java index d592cd9e86c3..afeab1d485c8 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/hestia/behavioral/knowledgesource/CombineChangeBlocks.java +++ b/src/main/java/de/tum/in/www1/artemis/service/hestia/behavioral/knowledgesource/CombineChangeBlocks.java @@ -38,8 +38,8 @@ public boolean executeAction() throws BehavioralSolutionEntryGenerationException if (i < currentChangeBlocks.size() - 1) { var nextChangeBlock = currentChangeBlocks.get(i + 1); if (currentChangeBlock.intersectsOrTouches(nextChangeBlock)) { - var lines = new TreeSet<>(currentChangeBlock.getLines()); - lines.addAll(nextChangeBlock.getLines()); + var lines = new TreeSet<>(currentChangeBlock.lines()); + lines.addAll(nextChangeBlock.lines()); newChangeBlocks.add(new GroupedFile.ChangeBlock(lines)); // Skip the next change block, as it has already been processed i++; diff --git a/src/main/java/de/tum/in/www1/artemis/service/hestia/behavioral/knowledgesource/CreateSolutionEntries.java b/src/main/java/de/tum/in/www1/artemis/service/hestia/behavioral/knowledgesource/CreateSolutionEntries.java index 332c9cf0c772..7411b2d3e0c2 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/hestia/behavioral/knowledgesource/CreateSolutionEntries.java +++ b/src/main/java/de/tum/in/www1/artemis/service/hestia/behavioral/knowledgesource/CreateSolutionEntries.java @@ -44,12 +44,12 @@ private ProgrammingExerciseSolutionEntry createSolutionEntry(GroupedFile grouped var solutionEntry = new ProgrammingExerciseSolutionEntry(); // Set temporary id, as equals checks won't work otherwise solutionEntry.setId(0L); - solutionEntry.setLine(changeBlock.getLines().first()); + solutionEntry.setLine(changeBlock.lines().first()); solutionEntry.setFilePath(groupedFile.getFilePath()); solutionEntry.setTestCase(groupedFile.getTestCase()); var fileContent = groupedFile.getFileContent(); if (fileContent != null) { - var code = Arrays.stream(fileContent.split("\n")).skip(changeBlock.getLines().first() - 1).limit(changeBlock.getLines().size()).collect(Collectors.joining("\n")); + var code = Arrays.stream(fileContent.split("\n")).skip(changeBlock.lines().first() - 1).limit(changeBlock.lines().size()).collect(Collectors.joining("\n")); solutionEntry.setCode(code); } return solutionEntry; diff --git a/src/main/java/de/tum/in/www1/artemis/service/hestia/structural/StructuralTestCaseService.java b/src/main/java/de/tum/in/www1/artemis/service/hestia/structural/StructuralTestCaseService.java index 5bdd6f7ce06c..3d520c16e448 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/hestia/structural/StructuralTestCaseService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/hestia/structural/StructuralTestCaseService.java @@ -81,8 +81,8 @@ public List generateStructuralSolutionEntries( if (solutionParticipation.isEmpty()) { return Collections.emptyList(); } - solutionRepository = gitService.getOrCheckoutRepository(solutionParticipation.get().getVcsRepositoryUrl(), true); - testRepository = gitService.getOrCheckoutRepository(programmingExercise.getVcsTestRepositoryUrl(), true); + solutionRepository = gitService.getOrCheckoutRepository(solutionParticipation.get().getVcsRepositoryUri(), true); + testRepository = gitService.getOrCheckoutRepository(programmingExercise.getVcsTestRepositoryUri(), true); gitService.resetToOriginHead(solutionRepository); gitService.pullIgnoreConflicts(solutionRepository); diff --git a/src/main/java/de/tum/in/www1/artemis/service/iris/session/IrisChatSessionService.java b/src/main/java/de/tum/in/www1/artemis/service/iris/session/IrisChatSessionService.java index c3f7dbe06423..c67685c0bd30 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/iris/session/IrisChatSessionService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/iris/session/IrisChatSessionService.java @@ -204,7 +204,7 @@ private void addDiffAndTemplatesForStudentAndExerciseIfPossible(User student, Pr } if (studentParticipations.isEmpty()) { try { - templateRepo = gitService.getOrCheckoutRepository(templateParticipation.get().getVcsRepositoryUrl(), true); + templateRepo = gitService.getOrCheckoutRepository(templateParticipation.get().getVcsRepositoryUri(), true); } catch (GitAPIException e) { throw new InternalServerErrorException("Iris cannot function without template participation"); @@ -214,8 +214,8 @@ private void addDiffAndTemplatesForStudentAndExerciseIfPossible(User student, Pr } try { - templateRepo = gitService.getOrCheckoutRepository(templateParticipation.get().getVcsRepositoryUrl(), true); - studentRepo = gitService.getOrCheckoutRepository(studentParticipations.get(studentParticipations.size() - 1).getVcsRepositoryUrl(), true); + templateRepo = gitService.getOrCheckoutRepository(templateParticipation.get().getVcsRepositoryUri(), true); + studentRepo = gitService.getOrCheckoutRepository(studentParticipations.get(studentParticipations.size() - 1).getVcsRepositoryUri(), true); } catch (GitAPIException e) { throw new InternalServerErrorException("Could not fetch existing student or template participation"); diff --git a/src/main/java/de/tum/in/www1/artemis/service/iris/session/IrisCodeEditorSessionService.java b/src/main/java/de/tum/in/www1/artemis/service/iris/session/IrisCodeEditorSessionService.java index b93f70c7692d..dd30c5b898a8 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/iris/session/IrisCodeEditorSessionService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/iris/session/IrisCodeEditorSessionService.java @@ -383,7 +383,7 @@ private Repository templateRepository(ProgrammingExercise exercise) { * @return The test repository */ private Repository testRepository(ProgrammingExercise exercise) { - return Optional.ofNullable(exercise.getVcsTestRepositoryUrl()).map(this::repositoryAt).orElseThrow(); + return Optional.ofNullable(exercise.getVcsTestRepositoryUri()).map(this::repositoryAt).orElseThrow(); } private Repository repositoryFor(ProgrammingExercise exercise, ExerciseComponent component) { @@ -403,7 +403,7 @@ private Repository repositoryFor(ProgrammingExercise exercise, ExerciseComponent * @return The repository */ private Repository repositoryAt(ProgrammingExerciseParticipation participation) { - var url = participation.getVcsRepositoryUrl(); + var url = participation.getVcsRepositoryUri(); try { // This check reduces the amount of REST-calls that retrieve the default branch of a repository. // Retrieving the default branch is not necessary if the repository is already cached. @@ -427,7 +427,7 @@ private Repository repositoryAt(ProgrammingExerciseParticipation participation) * @param url The URL to fetch the repository for * @return The repository */ - private Repository repositoryAt(VcsRepositoryUrl url) { + private Repository repositoryAt(VcsRepositoryUri url) { try { return gitService.getOrCheckoutRepository(url, true); } diff --git a/src/main/java/de/tum/in/www1/artemis/service/notifications/push_notifications/FirebasePushNotificationService.java b/src/main/java/de/tum/in/www1/artemis/service/notifications/push_notifications/FirebasePushNotificationService.java index d2cb8e520729..fae2c844865b 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/notifications/push_notifications/FirebasePushNotificationService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/notifications/push_notifications/FirebasePushNotificationService.java @@ -38,7 +38,7 @@ void sendNotificationRequestsToEndpoint(List requests, // The relay server accepts at most 500 messages per batch List> batches = Lists.partition(requests, 500); var futures = batches.stream().map(batch -> CompletableFuture.runAsync(() -> sendSpecificNotificationRequestsToEndpoint(batch, relayBaseUrl))).toList() - .toArray(new CompletableFuture[0]); + .toArray(CompletableFuture[]::new); CompletableFuture.allOf(futures); } diff --git a/src/main/java/de/tum/in/www1/artemis/service/notifications/push_notifications/PushNotificationService.java b/src/main/java/de/tum/in/www1/artemis/service/notifications/push_notifications/PushNotificationService.java index cc5dfaec53b3..1048403e5ce5 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/notifications/push_notifications/PushNotificationService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/notifications/push_notifications/PushNotificationService.java @@ -73,7 +73,7 @@ protected PushNotificationService(RestTemplate restTemplate) { void sendNotificationRequestsToEndpoint(List requests, String relayServerBaseUrl) { var futures = requests.stream() .map(request -> CompletableFuture.runAsync(() -> sendSpecificNotificationRequestsToEndpoint(Collections.singletonList(request), relayServerBaseUrl))).toList() - .toArray(new CompletableFuture[0]); + .toArray(CompletableFuture[]::new); CompletableFuture.allOf(futures); } diff --git a/src/main/java/de/tum/in/www1/artemis/service/plagiarism/ProgrammingPlagiarismDetectionService.java b/src/main/java/de/tum/in/www1/artemis/service/plagiarism/ProgrammingPlagiarismDetectionService.java index 25b13108afb9..da375c843e01 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/plagiarism/ProgrammingPlagiarismDetectionService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/plagiarism/ProgrammingPlagiarismDetectionService.java @@ -38,7 +38,7 @@ import de.tum.in.www1.artemis.repository.StudentParticipationRepository; import de.tum.in.www1.artemis.service.AuthorizationCheckService; import de.tum.in.www1.artemis.service.FileService; -import de.tum.in.www1.artemis.service.UrlService; +import de.tum.in.www1.artemis.service.UriService; import de.tum.in.www1.artemis.service.connectors.GitService; import de.tum.in.www1.artemis.service.export.ProgrammingExerciseExportService; import de.tum.in.www1.artemis.service.hestia.ProgrammingExerciseGitDiffReportService; @@ -70,7 +70,7 @@ public class ProgrammingPlagiarismDetectionService { private final PlagiarismCacheService plagiarismCacheService; - private final UrlService urlService; + private final UriService uriService; private final ProgrammingExerciseGitDiffReportService programmingExerciseGitDiffReportService; @@ -78,7 +78,7 @@ public class ProgrammingPlagiarismDetectionService { public ProgrammingPlagiarismDetectionService(FileService fileService, ProgrammingExerciseRepository programmingExerciseRepository, GitService gitService, StudentParticipationRepository studentParticipationRepository, ProgrammingExerciseExportService programmingExerciseExportService, - PlagiarismWebsocketService plagiarismWebsocketService, PlagiarismCacheService plagiarismCacheService, UrlService urlService, + PlagiarismWebsocketService plagiarismWebsocketService, PlagiarismCacheService plagiarismCacheService, UriService uriService, ProgrammingExerciseGitDiffReportService programmingExerciseGitDiffReportService, AuthorizationCheckService authCheckService) { this.fileService = fileService; this.programmingExerciseRepository = programmingExerciseRepository; @@ -87,7 +87,7 @@ public ProgrammingPlagiarismDetectionService(FileService fileService, Programmin this.programmingExerciseExportService = programmingExerciseExportService; this.plagiarismWebsocketService = plagiarismWebsocketService; this.plagiarismCacheService = plagiarismCacheService; - this.urlService = urlService; + this.uriService = uriService; this.programmingExerciseGitDiffReportService = programmingExerciseGitDiffReportService; this.authCheckService = authCheckService; } @@ -188,7 +188,7 @@ private JPlagResult computeJPlagResult(ProgrammingExercise programmingExercise, final var projectKey = programmingExercise.getProjectKey(); final var repoFolder = targetPath.resolve(projectKey).toFile(); final var programmingLanguage = getJPlagProgrammingLanguage(programmingExercise); - final var templateRepoName = urlService.getRepositorySlugFromRepositoryUrl(programmingExercise.getTemplateParticipation().getVcsRepositoryUrl()); + final var templateRepoName = uriService.getRepositorySlugFromRepositoryUri(programmingExercise.getTemplateParticipation().getVcsRepositoryUri()); JPlagOptions options = new JPlagOptions(programmingLanguage, Set.of(repoFolder), Set.of()) // JPlag expects a value between 0.0 and 1.0 @@ -325,7 +325,7 @@ public List filterStudentParticipationsForComp return studentParticipations.parallelStream().filter(participation -> !participation.isPracticeMode()) .filter(participation -> participation instanceof ProgrammingExerciseParticipation).filter(participation -> participation.getStudent().isPresent()) .filter(participation -> !authCheckService.isAtLeastTeachingAssistantForExercise(programmingExercise, participation.getStudent().get())) - .map(participation -> (ProgrammingExerciseParticipation) participation).filter(participation -> participation.getVcsRepositoryUrl() != null) + .map(participation -> (ProgrammingExerciseParticipation) participation).filter(participation -> participation.getVcsRepositoryUri() != null) .filter(participation -> { Submission submission = participation.findLatestSubmission().orElse(null); // filter empty submissions @@ -344,7 +344,7 @@ private Optional cloneTemplateRepository(ProgrammingExercise program return Optional.of(templateRepo); } catch (GitException | GitAPIException ex) { - log.error("Clone template repository {} in exercise '{}' did not work as expected: {}", programmingExercise.getTemplateParticipation().getVcsRepositoryUrl(), + log.error("Clone template repository {} in exercise '{}' did not work as expected: {}", programmingExercise.getTemplateParticipation().getVcsRepositoryUri(), programmingExercise.getTitle(), ex.getMessage()); return Optional.empty(); } @@ -355,8 +355,8 @@ private boolean shouldAddRepo(int minimumSize, Repository repo, Optional= minimumSize; } @@ -388,7 +388,7 @@ private List downloadRepositories(ProgrammingExercise programmingExe } } catch (GitException | GitAPIException | InvalidPathException ex) { - log.error("Clone student repository {} in exercise '{}' did not work as expected: {}", participation.getVcsRepositoryUrl(), programmingExercise.getTitle(), + log.error("Clone student repository {} in exercise '{}' did not work as expected: {}", participation.getVcsRepositoryUri(), programmingExercise.getTitle(), ex.getMessage()); } }); diff --git a/src/main/java/de/tum/in/www1/artemis/service/programming/ProgrammingExerciseImportBasicService.java b/src/main/java/de/tum/in/www1/artemis/service/programming/ProgrammingExerciseImportBasicService.java index 183442276126..f139eabc44f4 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/programming/ProgrammingExerciseImportBasicService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/programming/ProgrammingExerciseImportBasicService.java @@ -179,14 +179,14 @@ private void prepareBasicExerciseInformation(final ProgrammingExercise templateE } /** - * Sets up the test repository for a new exercise by setting the repository URL. This does not create the actual + * Sets up the test repository for a new exercise by setting the repository URI. This does not create the actual * repository on the version control server! * * @param newExercise the new exercises that should be created during import */ private void setupTestRepository(ProgrammingExercise newExercise) { final var testRepoName = newExercise.generateRepositoryName(RepositoryType.TESTS); - newExercise.setTestRepositoryUrl(versionControlService.orElseThrow().getCloneRepositoryUrl(newExercise.getProjectKey(), testRepoName).toString()); + newExercise.setTestRepositoryUri(versionControlService.orElseThrow().getCloneRepositoryUri(newExercise.getProjectKey(), testRepoName).toString()); } /** diff --git a/src/main/java/de/tum/in/www1/artemis/service/programming/ProgrammingExerciseImportFromFileService.java b/src/main/java/de/tum/in/www1/artemis/service/programming/ProgrammingExerciseImportFromFileService.java index 36c030252b1b..dee7dc4ebc3a 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/programming/ProgrammingExerciseImportFromFileService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/programming/ProgrammingExerciseImportFromFileService.java @@ -118,9 +118,9 @@ private void copyEmbeddedFiles(Path importExerciseDir) throws IOException { private void importRepositoriesFromFile(ProgrammingExercise newExercise, Path basePath, String oldExerciseShortName, User user) throws IOException, GitAPIException, URISyntaxException { - Repository templateRepo = gitService.getOrCheckoutRepository(new VcsRepositoryUrl(newExercise.getTemplateRepositoryUrl()), false); - Repository solutionRepo = gitService.getOrCheckoutRepository(new VcsRepositoryUrl(newExercise.getSolutionRepositoryUrl()), false); - Repository testRepo = gitService.getOrCheckoutRepository(new VcsRepositoryUrl(newExercise.getTestRepositoryUrl()), false); + Repository templateRepo = gitService.getOrCheckoutRepository(new VcsRepositoryUri(newExercise.getTemplateRepositoryUri()), false); + Repository solutionRepo = gitService.getOrCheckoutRepository(new VcsRepositoryUri(newExercise.getSolutionRepositoryUri()), false); + Repository testRepo = gitService.getOrCheckoutRepository(new VcsRepositoryUri(newExercise.getTestRepositoryUri()), false); copyImportedExerciseContentToRepositories(templateRepo, solutionRepo, testRepo, basePath); replaceImportedExerciseShortName(Map.of(oldExerciseShortName, newExercise.getShortName()), templateRepo, solutionRepo, testRepo); diff --git a/src/main/java/de/tum/in/www1/artemis/service/programming/ProgrammingExerciseImportService.java b/src/main/java/de/tum/in/www1/artemis/service/programming/ProgrammingExerciseImportService.java index 14b42786a60d..a88218afc681 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/programming/ProgrammingExerciseImportService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/programming/ProgrammingExerciseImportService.java @@ -23,7 +23,7 @@ import de.tum.in.www1.artemis.repository.AuxiliaryRepositoryRepository; import de.tum.in.www1.artemis.repository.UserRepository; import de.tum.in.www1.artemis.service.FileService; -import de.tum.in.www1.artemis.service.UrlService; +import de.tum.in.www1.artemis.service.UriService; import de.tum.in.www1.artemis.service.connectors.GitService; import de.tum.in.www1.artemis.service.connectors.ci.ContinuousIntegrationService; import de.tum.in.www1.artemis.service.connectors.ci.ContinuousIntegrationTriggerService; @@ -53,7 +53,7 @@ public class ProgrammingExerciseImportService { private final AuxiliaryRepositoryRepository auxiliaryRepositoryRepository; - private final UrlService urlService; + private final UriService uriService; private final TemplateUpgradePolicy templateUpgradePolicy; @@ -62,7 +62,7 @@ public class ProgrammingExerciseImportService { public ProgrammingExerciseImportService(Optional versionControlService, Optional continuousIntegrationService, Optional continuousIntegrationTriggerService, ProgrammingExerciseService programmingExerciseService, ProgrammingExerciseTaskService programmingExerciseTaskService, GitService gitService, FileService fileService, UserRepository userRepository, - AuxiliaryRepositoryRepository auxiliaryRepositoryRepository, UrlService urlService, TemplateUpgradePolicy templateUpgradePolicy, + AuxiliaryRepositoryRepository auxiliaryRepositoryRepository, UriService uriService, TemplateUpgradePolicy templateUpgradePolicy, ProgrammingExerciseImportBasicService programmingExerciseImportBasicService) { this.versionControlService = versionControlService; this.continuousIntegrationService = continuousIntegrationService; @@ -73,7 +73,7 @@ public ProgrammingExerciseImportService(Optional versionC this.fileService = fileService; this.userRepository = userRepository; this.auxiliaryRepositoryRepository = auxiliaryRepositoryRepository; - this.urlService = urlService; + this.uriService = uriService; this.templateUpgradePolicy = templateUpgradePolicy; this.programmingExerciseImportBasicService = programmingExerciseImportBasicService; } @@ -93,9 +93,9 @@ public void importRepositories(final ProgrammingExercise templateExercise, final VersionControlService versionControl = versionControlService.orElseThrow(); versionControl.createProjectForExercise(newExercise); // Copy all repositories - String templateRepoName = urlService.getRepositorySlugFromRepositoryUrlString(templateExercise.getTemplateRepositoryUrl()); - String testRepoName = urlService.getRepositorySlugFromRepositoryUrlString(templateExercise.getTestRepositoryUrl()); - String solutionRepoName = urlService.getRepositorySlugFromRepositoryUrlString(templateExercise.getSolutionRepositoryUrl()); + String templateRepoName = uriService.getRepositorySlugFromRepositoryUriString(templateExercise.getTemplateRepositoryUri()); + String testRepoName = uriService.getRepositorySlugFromRepositoryUriString(templateExercise.getTestRepositoryUri()); + String solutionRepoName = uriService.getRepositorySlugFromRepositoryUriString(templateExercise.getSolutionRepositoryUri()); String sourceBranch = versionControl.getOrRetrieveBranchOfExercise(templateExercise); @@ -107,16 +107,16 @@ public void importRepositories(final ProgrammingExercise templateExercise, final List auxRepos = templateExercise.getAuxiliaryRepositories(); for (int i = 0; i < auxRepos.size(); i++) { AuxiliaryRepository auxRepo = auxRepos.get(i); - var repoUrl = versionControl.copyRepository(sourceProjectKey, auxRepo.getRepositoryName(), sourceBranch, targetProjectKey, auxRepo.getName()).toString(); + var repoUri = versionControl.copyRepository(sourceProjectKey, auxRepo.getRepositoryName(), sourceBranch, targetProjectKey, auxRepo.getName()).toString(); AuxiliaryRepository newAuxRepo = newExercise.getAuxiliaryRepositories().get(i); - newAuxRepo.setRepositoryUrl(repoUrl); + newAuxRepo.setRepositoryUri(repoUri); auxiliaryRepositoryRepository.save(newAuxRepo); } // Unprotect the default branch of the template exercise repo. - VcsRepositoryUrl templateVcsRepositoryUrl = newExercise.getVcsTemplateRepositoryUrl(); + VcsRepositoryUri templateVcsRepositoryUri = newExercise.getVcsTemplateRepositoryUri(); String templateVcsRepositoryBranch = versionControl.getOrRetrieveBranchOfExercise(templateExercise); - versionControl.unprotectBranch(templateVcsRepositoryUrl, templateVcsRepositoryBranch); + versionControl.unprotectBranch(templateVcsRepositoryUri, templateVcsRepositoryBranch); // Add the necessary hooks notifying Artemis about changes after commits have been pushed versionControl.addWebHooksForExercise(newExercise); @@ -142,12 +142,12 @@ public void importBuildPlans(final ProgrammingExercise templateExercise, final P final var solutionParticipation = newExercise.getSolutionParticipation(); final var targetExerciseProjectKey = newExercise.getProjectKey(); - // Clone all build plans, enable them and set up the initial participations, i.e. setting the correct repo URLs and + // Clone all build plans, enable them and set up the initial participations, i.e. setting the correct repo URIs and // running the plan for the first time cloneAndEnableAllBuildPlans(templateExercise, newExercise); - updatePlanRepositoriesInBuildPlans(newExercise, templateParticipation, solutionParticipation, targetExerciseProjectKey, templateExercise.getTemplateRepositoryUrl(), - templateExercise.getSolutionRepositoryUrl(), templateExercise.getTestRepositoryUrl(), templateExercise.getAuxiliaryRepositoriesForBuildPlan()); + updatePlanRepositoriesInBuildPlans(newExercise, templateParticipation, solutionParticipation, targetExerciseProjectKey, templateExercise.getTemplateRepositoryUri(), + templateExercise.getSolutionRepositoryUri(), templateExercise.getTestRepositoryUri(), templateExercise.getAuxiliaryRepositoriesForBuildPlan()); ContinuousIntegrationTriggerService triggerService = continuousIntegrationTriggerService.orElseThrow(); triggerService.triggerBuild(templateParticipation); @@ -155,26 +155,26 @@ public void importBuildPlans(final ProgrammingExercise templateExercise, final P } private void updatePlanRepositoriesInBuildPlans(ProgrammingExercise newExercise, TemplateProgrammingExerciseParticipation templateParticipation, - SolutionProgrammingExerciseParticipation solutionParticipation, String targetExerciseProjectKey, String oldExerciseRepoUrl, String oldSolutionRepoUrl, - String oldTestRepoUrl, List oldBuildPlanAuxiliaryRepositories) { + SolutionProgrammingExerciseParticipation solutionParticipation, String targetExerciseProjectKey, String oldExerciseRepoUri, String oldSolutionRepoUri, + String oldTestRepoUri, List oldBuildPlanAuxiliaryRepositories) { String newExerciseBranch = versionControlService.orElseThrow().getOrRetrieveBranchOfExercise(newExercise); // update 2 repositories for the BASE build plan --> adapt the triggers so that only the assignment repo (and not the tests' repo) will trigger the BASE build plan ContinuousIntegrationService continuousIntegration = continuousIntegrationService.orElseThrow(); continuousIntegration.updatePlanRepository(targetExerciseProjectKey, templateParticipation.getBuildPlanId(), ASSIGNMENT_REPO_NAME, targetExerciseProjectKey, - newExercise.getTemplateRepositoryUrl(), oldExerciseRepoUrl, newExerciseBranch); + newExercise.getTemplateRepositoryUri(), oldExerciseRepoUri, newExerciseBranch); continuousIntegration.updatePlanRepository(targetExerciseProjectKey, templateParticipation.getBuildPlanId(), TEST_REPO_NAME, targetExerciseProjectKey, - newExercise.getTestRepositoryUrl(), oldTestRepoUrl, newExerciseBranch); + newExercise.getTestRepositoryUri(), oldTestRepoUri, newExerciseBranch); updateAuxiliaryRepositoriesForNewExercise(newExercise.getAuxiliaryRepositoriesForBuildPlan(), oldBuildPlanAuxiliaryRepositories, templateParticipation, targetExerciseProjectKey, newExercise); // update 2 repositories for the SOLUTION build plan continuousIntegration.updatePlanRepository(targetExerciseProjectKey, solutionParticipation.getBuildPlanId(), ASSIGNMENT_REPO_NAME, targetExerciseProjectKey, - newExercise.getSolutionRepositoryUrl(), oldSolutionRepoUrl, newExerciseBranch); + newExercise.getSolutionRepositoryUri(), oldSolutionRepoUri, newExerciseBranch); continuousIntegration.updatePlanRepository(targetExerciseProjectKey, solutionParticipation.getBuildPlanId(), TEST_REPO_NAME, targetExerciseProjectKey, - newExercise.getTestRepositoryUrl(), oldTestRepoUrl, newExerciseBranch); + newExercise.getTestRepositoryUri(), oldTestRepoUri, newExerciseBranch); updateAuxiliaryRepositoriesForNewExercise(newExercise.getAuxiliaryRepositoriesForBuildPlan(), oldBuildPlanAuxiliaryRepositories, solutionParticipation, targetExerciseProjectKey, newExercise); @@ -187,7 +187,7 @@ private void updateAuxiliaryRepositoriesForNewExercise(List AuxiliaryRepository oldAuxiliaryRepository = oldRepositories.get(i); String auxiliaryBranch = versionControlService.orElseThrow().getOrRetrieveBranchOfExercise(newExercise); continuousIntegrationService.orElseThrow().updatePlanRepository(targetExerciseProjectKey, participation.getBuildPlanId(), newAuxiliaryRepository.getName(), - targetExerciseProjectKey, newAuxiliaryRepository.getRepositoryUrl(), oldAuxiliaryRepository.getRepositoryUrl(), auxiliaryBranch); + targetExerciseProjectKey, newAuxiliaryRepository.getRepositoryUri(), oldAuxiliaryRepository.getRepositoryUri(), auxiliaryBranch); } } @@ -253,8 +253,8 @@ private void adjustProjectNames(ProgrammingExercise templateExercise, Programmin * @throws GitAPIException If the checkout/push of one repository fails */ private void adjustProjectName(Map replacements, String projectKey, String repositoryName, User user) throws GitAPIException { - final var repositoryUrl = versionControlService.orElseThrow().getCloneRepositoryUrl(projectKey, repositoryName); - Repository repository = gitService.getOrCheckoutRepository(repositoryUrl, true); + final var repositoryUri = versionControlService.orElseThrow().getCloneRepositoryUri(projectKey, repositoryName); + Repository repository = gitService.getOrCheckoutRepository(repositoryUri, true); fileService.replaceVariablesInFileRecursive(repository.getLocalPath().toAbsolutePath(), replacements, List.of("gradle-wrapper.jar")); gitService.stageAllChanges(repository); gitService.commitAndPush(repository, "Template adjusted by Artemis", true, user); diff --git a/src/main/java/de/tum/in/www1/artemis/service/programming/ProgrammingExerciseParticipationService.java b/src/main/java/de/tum/in/www1/artemis/service/programming/ProgrammingExerciseParticipationService.java index 8512cda337f3..94484c09c4a7 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/programming/ProgrammingExerciseParticipationService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/programming/ProgrammingExerciseParticipationService.java @@ -208,7 +208,7 @@ public ProgrammingExerciseParticipation findProgrammingExerciseParticipationWith /** * Setup the initial solution participation for an exercise. Creates the new participation entity and sets - * the correct build plan ID and repository URL. Saves the participation after all values have been set. + * the correct build plan ID and repository URI. Saves the participation after all values have been set. * * @param newExercise The new exercise for which a participation should be generated */ @@ -217,14 +217,14 @@ public void setupInitialSolutionParticipation(ProgrammingExercise newExercise) { SolutionProgrammingExerciseParticipation solutionParticipation = new SolutionProgrammingExerciseParticipation(); newExercise.setSolutionParticipation(solutionParticipation); solutionParticipation.setBuildPlanId(newExercise.generateBuildPlanId(BuildPlanType.SOLUTION)); - solutionParticipation.setRepositoryUrl(versionControlService.orElseThrow().getCloneRepositoryUrl(newExercise.getProjectKey(), solutionRepoName).toString()); + solutionParticipation.setRepositoryUri(versionControlService.orElseThrow().getCloneRepositoryUri(newExercise.getProjectKey(), solutionRepoName).toString()); solutionParticipation.setProgrammingExercise(newExercise); solutionParticipationRepository.save(solutionParticipation); } /** * Setup the initial template participation for an exercise. Creates the new participation entity and sets - * the correct build plan ID and repository URL. Saves the participation after all values have been set. + * the correct build plan ID and repository URI. Saves the participation after all values have been set. * * @param newExercise The new exercise for which a participation should be generated */ @@ -232,7 +232,7 @@ public void setupInitialTemplateParticipation(ProgrammingExercise newExercise) { final String exerciseRepoName = newExercise.generateRepositoryName(RepositoryType.TEMPLATE); TemplateProgrammingExerciseParticipation templateParticipation = new TemplateProgrammingExerciseParticipation(); templateParticipation.setBuildPlanId(newExercise.generateBuildPlanId(BuildPlanType.TEMPLATE)); - templateParticipation.setRepositoryUrl(versionControlService.orElseThrow().getCloneRepositoryUrl(newExercise.getProjectKey(), exerciseRepoName).toString()); + templateParticipation.setRepositoryUri(versionControlService.orElseThrow().getCloneRepositoryUri(newExercise.getProjectKey(), exerciseRepoName).toString()); templateParticipation.setProgrammingExercise(newExercise); newExercise.setTemplateParticipation(templateParticipation); templateParticipationRepository.save(templateParticipation); @@ -247,7 +247,7 @@ public void setupInitialTemplateParticipation(ProgrammingExercise newExercise) { */ public void lockStudentRepository(ProgrammingExercise programmingExercise, ProgrammingExerciseStudentParticipation participation) { if (participation.getInitializationState().hasCompletedState(InitializationState.REPO_CONFIGURED)) { - versionControlService.orElseThrow().setRepositoryPermissionsToReadOnly(participation.getVcsRepositoryUrl(), programmingExercise.getProjectKey(), + versionControlService.orElseThrow().setRepositoryPermissionsToReadOnly(participation.getVcsRepositoryUri(), programmingExercise.getProjectKey(), participation.getStudents()); } else { @@ -316,7 +316,7 @@ public void lockStudentRepositoryAndParticipation(ProgrammingExercise programmin public void unlockStudentRepository(ProgrammingExerciseStudentParticipation participation) { if (participation.getInitializationState().hasCompletedState(InitializationState.REPO_CONFIGURED)) { for (User user : participation.getStudents()) { - versionControlService.orElseThrow().addMemberToRepository(participation.getVcsRepositoryUrl(), user, VersionControlRepositoryPermission.REPO_WRITE); + versionControlService.orElseThrow().addMemberToRepository(participation.getVcsRepositoryUri(), user, VersionControlRepositoryPermission.REPO_WRITE); } } else { @@ -377,7 +377,7 @@ public void stashChangesInStudentRepositoryAfterDueDateHasPassed(ProgrammingExer * @param targetURL the repository where all files should be replaced * @param sourceURL the repository that should be used as source for all files */ - public void resetRepository(VcsRepositoryUrl targetURL, VcsRepositoryUrl sourceURL) throws GitAPIException, IOException { + public void resetRepository(VcsRepositoryUri targetURL, VcsRepositoryUri sourceURL) throws GitAPIException, IOException { Repository targetRepo = gitService.getOrCheckoutRepository(targetURL, true); Repository sourceRepo = gitService.getOrCheckoutRepository(sourceURL, true); @@ -404,8 +404,8 @@ public void resetRepository(VcsRepositoryUrl targetURL, VcsRepositoryUrl sourceU * participation. * * @param exercise the exercise for which to get the participation. - * @param repositoryTypeOrUserName the repository type ("exercise", "solution", or "tests") or username (e.g. "artemis_test_user_1") as extracted from the repository URL. - * @param isPracticeRepository whether the repository is a practice repository, i.e. the repository URL contains "-practice-". + * @param repositoryTypeOrUserName the repository type ("exercise", "solution", or "tests") or username (e.g. "artemis_test_user_1") as extracted from the repository URI. + * @param isPracticeRepository whether the repository is a practice repository, i.e. the repository URI contains "-practice-". * @param withSubmissions whether submissions should be loaded with the participation. This is needed for the local CI system. * @return the participation. * @throws EntityNotFoundException if the participation could not be found. @@ -443,7 +443,7 @@ public ProgrammingExerciseParticipation getParticipationForRepository(Programmin // If the exercise is an exam exercise and the repository's owner is at least an editor, the repository could be a test run repository, or it could be the instructor's // assignment repository. - // There is no way to tell from the repository URL, and only one participation will be created, even if both are used. + // There is no way to tell from the repository URI, and only one participation will be created, even if both are used. // This participation has "testRun = true" set if the test run was created first, and "testRun = false" set if the instructor's assignment repository was created first. // If the exercise is an exam exercise, and the repository's owner is at least an editor, get the participation without regard for the testRun flag. boolean isExamEditorRepository = exercise.isExamExercise() @@ -467,10 +467,10 @@ public ProgrammingExerciseParticipation getParticipationForRepository(Programmin */ public List getCommitInfos(ProgrammingExerciseStudentParticipation participation) { try { - return gitService.getCommitInfos(participation.getVcsRepositoryUrl()); + return gitService.getCommitInfos(participation.getVcsRepositoryUri()); } catch (GitAPIException e) { - log.error("Could not get commit infos for participation " + participation.getId() + " with repository url " + participation.getVcsRepositoryUrl()); + log.error("Could not get commit infos for participation " + participation.getId() + " with repository uri " + participation.getVcsRepositoryUri()); return List.of(); } } diff --git a/src/main/java/de/tum/in/www1/artemis/service/programming/ProgrammingExerciseRepositoryService.java b/src/main/java/de/tum/in/www1/artemis/service/programming/ProgrammingExerciseRepositoryService.java index 2a02704debb3..f4c09ef3d395 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/programming/ProgrammingExerciseRepositoryService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/programming/ProgrammingExerciseRepositoryService.java @@ -118,8 +118,8 @@ private RepositoryResources getRepositoryResources(final ProgrammingExercise pro final ProjectType projectType = programmingExercise.getProjectType(); final Path projectTypeTemplateDir = getTemplateDirectoryForRepositoryType(repositoryType); - final VcsRepositoryUrl repoUrl = programmingExercise.getRepositoryURL(repositoryType); - final Repository repo = gitService.getOrCheckoutRepository(repoUrl, true); + final VcsRepositoryUri repoUri = programmingExercise.getRepositoryURL(repositoryType); + final Repository repo = gitService.getOrCheckoutRepository(repoUri, true); // Get path, files and prefix for the programming-language dependent files. They are copied first. final Path generalTemplatePath = ProgrammingExerciseService.getProgrammingLanguageTemplatePath(programmingExercise.getProgrammingLanguage()) @@ -180,10 +180,10 @@ private void setupRepositories(final ProgrammingExercise programmingExercise, fi try { setupTemplateAndPush(exerciseResources, "Exercise", programmingExercise, exerciseCreator); // The template repo can be re-written, so we can unprotect the default branch. - final var templateVcsRepositoryUrl = programmingExercise.getVcsTemplateRepositoryUrl(); + final var templateVcsRepositoryUri = programmingExercise.getVcsTemplateRepositoryUri(); VersionControlService versionControl = versionControlService.orElseThrow(); final String templateBranch = versionControl.getOrRetrieveBranchOfExercise(programmingExercise); - versionControl.unprotectBranch(templateVcsRepositoryUrl, templateBranch); + versionControl.unprotectBranch(templateVcsRepositoryUri, templateBranch); setupTemplateAndPush(solutionResources, "Solution", programmingExercise, exerciseCreator); setupTestTemplateAndPush(testResources, programmingExercise, exerciseCreator); @@ -221,9 +221,9 @@ private void createAndInitializeAuxiliaryRepositories(final String projectKey, f for (final AuxiliaryRepository repo : programmingExercise.getAuxiliaryRepositories()) { final String repositoryName = programmingExercise.generateRepositoryName(repo.getName()); versionControlService.orElseThrow().createRepository(projectKey, repositoryName, null); - repo.setRepositoryUrl(versionControlService.orElseThrow().getCloneRepositoryUrl(programmingExercise.getProjectKey(), repositoryName).toString()); + repo.setRepositoryUri(versionControlService.orElseThrow().getCloneRepositoryUri(programmingExercise.getProjectKey(), repositoryName).toString()); - final Repository vcsRepository = gitService.getOrCheckoutRepository(repo.getVcsRepositoryUrl(), true); + final Repository vcsRepository = gitService.getOrCheckoutRepository(repo.getVcsRepositoryUri(), true); gitService.commitAndPush(vcsRepository, SETUP_COMMIT_MESSAGE, true, null); } } @@ -721,23 +721,23 @@ else if (moreLenientStartDate || moreLenientDueDate) { * @param programmingExercise The programming exercise for which the repositories should be deleted. */ void deleteRepositories(final ProgrammingExercise programmingExercise) { - if (programmingExercise.getTemplateRepositoryUrl() != null) { - final var templateRepositoryUrlAsUrl = programmingExercise.getVcsTemplateRepositoryUrl(); - versionControlService.orElseThrow().deleteRepository(templateRepositoryUrlAsUrl); + if (programmingExercise.getTemplateRepositoryUri() != null) { + final var templateRepositoryUriAsUrl = programmingExercise.getVcsTemplateRepositoryUri(); + versionControlService.orElseThrow().deleteRepository(templateRepositoryUriAsUrl); } - if (programmingExercise.getSolutionRepositoryUrl() != null) { - final var solutionRepositoryUrlAsUrl = programmingExercise.getVcsSolutionRepositoryUrl(); - versionControlService.orElseThrow().deleteRepository(solutionRepositoryUrlAsUrl); + if (programmingExercise.getSolutionRepositoryUri() != null) { + final var solutionRepositoryUriAsUrl = programmingExercise.getVcsSolutionRepositoryUri(); + versionControlService.orElseThrow().deleteRepository(solutionRepositoryUriAsUrl); } - if (programmingExercise.getTestRepositoryUrl() != null) { - final var testRepositoryUrlAsUrl = programmingExercise.getVcsTestRepositoryUrl(); - versionControlService.orElseThrow().deleteRepository(testRepositoryUrlAsUrl); + if (programmingExercise.getTestRepositoryUri() != null) { + final var testRepositoryUriAsUrl = programmingExercise.getVcsTestRepositoryUri(); + versionControlService.orElseThrow().deleteRepository(testRepositoryUriAsUrl); } // We also want to delete any auxiliary repositories programmingExercise.getAuxiliaryRepositories().forEach(repo -> { - if (repo.getRepositoryUrl() != null) { - versionControlService.orElseThrow().deleteRepository(repo.getVcsRepositoryUrl()); + if (repo.getRepositoryUri() != null) { + versionControlService.orElseThrow().deleteRepository(repo.getVcsRepositoryUri()); } }); @@ -758,17 +758,17 @@ void deleteRepositories(final ProgrammingExercise programmingExercise) { * @param programmingExercise The exercise for which the local repository copies should be deleted. */ void deleteLocalRepoCopies(final ProgrammingExercise programmingExercise) { - if (programmingExercise.getTemplateRepositoryUrl() != null) { - final var templateRepositoryUrlAsUrl = programmingExercise.getVcsTemplateRepositoryUrl(); - gitService.deleteLocalRepository(templateRepositoryUrlAsUrl); + if (programmingExercise.getTemplateRepositoryUri() != null) { + final var templateRepositoryUriAsUrl = programmingExercise.getVcsTemplateRepositoryUri(); + gitService.deleteLocalRepository(templateRepositoryUriAsUrl); } - if (programmingExercise.getSolutionRepositoryUrl() != null) { - final var solutionRepositoryUrlAsUrl = programmingExercise.getVcsSolutionRepositoryUrl(); - gitService.deleteLocalRepository(solutionRepositoryUrlAsUrl); + if (programmingExercise.getSolutionRepositoryUri() != null) { + final var solutionRepositoryUriAsUrl = programmingExercise.getVcsSolutionRepositoryUri(); + gitService.deleteLocalRepository(solutionRepositoryUriAsUrl); } - if (programmingExercise.getTestRepositoryUrl() != null) { - final var testRepositoryUrlAsUrl = programmingExercise.getVcsTestRepositoryUrl(); - gitService.deleteLocalRepository(testRepositoryUrlAsUrl); + if (programmingExercise.getTestRepositoryUri() != null) { + final var testRepositoryUriAsUrl = programmingExercise.getVcsTestRepositoryUri(); + gitService.deleteLocalRepository(testRepositoryUriAsUrl); } } } diff --git a/src/main/java/de/tum/in/www1/artemis/service/programming/ProgrammingExerciseService.java b/src/main/java/de/tum/in/www1/artemis/service/programming/ProgrammingExerciseService.java index 7647e6331af8..93de161be767 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/programming/ProgrammingExerciseService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/programming/ProgrammingExerciseService.java @@ -373,16 +373,16 @@ public void validateStaticCodeAnalysisSettings(ProgrammingExercise programmingEx public void setupBuildPlansForNewExercise(ProgrammingExercise programmingExercise, boolean isImportedFromFile) { String projectKey = programmingExercise.getProjectKey(); // Get URLs for repos - var exerciseRepoUrl = programmingExercise.getVcsTemplateRepositoryUrl(); - var testsRepoUrl = programmingExercise.getVcsTestRepositoryUrl(); - var solutionRepoUrl = programmingExercise.getVcsSolutionRepositoryUrl(); + var exerciseRepoUri = programmingExercise.getVcsTemplateRepositoryUri(); + var testsRepoUri = programmingExercise.getVcsTestRepositoryUri(); + var solutionRepoUri = programmingExercise.getVcsSolutionRepositoryUri(); ContinuousIntegrationService continuousIntegration = continuousIntegrationService.orElseThrow(); continuousIntegration.createProjectForExercise(programmingExercise); // template build plan - continuousIntegration.createBuildPlanForExercise(programmingExercise, TEMPLATE.getName(), exerciseRepoUrl, testsRepoUrl, solutionRepoUrl); + continuousIntegration.createBuildPlanForExercise(programmingExercise, TEMPLATE.getName(), exerciseRepoUri, testsRepoUri, solutionRepoUri); // solution build plan - continuousIntegration.createBuildPlanForExercise(programmingExercise, SOLUTION.getName(), solutionRepoUrl, testsRepoUrl, solutionRepoUrl); + continuousIntegration.createBuildPlanForExercise(programmingExercise, SOLUTION.getName(), solutionRepoUri, testsRepoUri, solutionRepoUri); // Give appropriate permissions for CI projects continuousIntegration.removeAllDefaultProjectPermissions(projectKey); @@ -440,15 +440,15 @@ private void setURLsAndBuildPlanIDsForNewExercise(ProgrammingExercise programmin VersionControlService versionControl = versionControlService.orElseThrow(); templateParticipation.setBuildPlanId(templatePlanId); // Set build plan id to newly created BaseBuild plan - templateParticipation.setRepositoryUrl(versionControl.getCloneRepositoryUrl(projectKey, exerciseRepoName).toString()); + templateParticipation.setRepositoryUri(versionControl.getCloneRepositoryUri(projectKey, exerciseRepoName).toString()); solutionParticipation.setBuildPlanId(solutionPlanId); - solutionParticipation.setRepositoryUrl(versionControl.getCloneRepositoryUrl(projectKey, solutionRepoName).toString()); - programmingExercise.setTestRepositoryUrl(versionControl.getCloneRepositoryUrl(projectKey, testRepoName).toString()); + solutionParticipation.setRepositoryUri(versionControl.getCloneRepositoryUri(projectKey, solutionRepoName).toString()); + programmingExercise.setTestRepositoryUri(versionControl.getCloneRepositoryUri(projectKey, testRepoName).toString()); } private void setURLsForAuxiliaryRepositoriesOfExercise(ProgrammingExercise programmingExercise) { - programmingExercise.getAuxiliaryRepositories().forEach(repo -> repo.setRepositoryUrl(versionControlService.orElseThrow() - .getCloneRepositoryUrl(programmingExercise.getProjectKey(), programmingExercise.generateRepositoryName(repo.getName())).toString())); + programmingExercise.getAuxiliaryRepositories().forEach(repo -> repo.setRepositoryUri(versionControlService.orElseThrow() + .getCloneRepositoryUri(programmingExercise.getProjectKey(), programmingExercise.generateRepositoryName(repo.getName())).toString())); } public static Path getProgrammingLanguageProjectTypePath(ProgrammingLanguage programmingLanguage, ProjectType projectType) { @@ -569,20 +569,20 @@ public ProgrammingExercise updateProblemStatement(ProgrammingExercise programmin * This method calls the StructureOracleGenerator, generates the string out of the JSON representation of the structure oracle of the programming exercise and returns true if * the file was updated or generated, false otherwise. This can happen if the contents of the file have not changed. * - * @param solutionRepoURL The URL of the solution repository. - * @param exerciseRepoURL The URL of the exercise repository. - * @param testRepoURL The URL of the tests' repository. + * @param solutionRepoUri The URL of the solution repository. + * @param exerciseRepoUri The URL of the exercise repository. + * @param testRepoUri The URL of the tests' repository. * @param testsPath The path to the tests' folder, e.g. the path inside the repository where the structure oracle file will be saved in. * @param user The user who has initiated the action * @return True, if the structure oracle was successfully generated or updated, false if no changes to the file were made. * @throws IOException If the URLs cannot be converted to actual {@link Path paths} * @throws GitAPIException If the checkout fails */ - public boolean generateStructureOracleFile(VcsRepositoryUrl solutionRepoURL, VcsRepositoryUrl exerciseRepoURL, VcsRepositoryUrl testRepoURL, String testsPath, User user) + public boolean generateStructureOracleFile(VcsRepositoryUri solutionRepoUri, VcsRepositoryUri exerciseRepoUri, VcsRepositoryUri testRepoUri, String testsPath, User user) throws IOException, GitAPIException { - Repository solutionRepository = gitService.getOrCheckoutRepository(solutionRepoURL, true); - Repository exerciseRepository = gitService.getOrCheckoutRepository(exerciseRepoURL, true); - Repository testRepository = gitService.getOrCheckoutRepository(testRepoURL, true); + Repository solutionRepository = gitService.getOrCheckoutRepository(solutionRepoUri, true); + Repository exerciseRepository = gitService.getOrCheckoutRepository(exerciseRepoUri, true); + Repository testRepository = gitService.getOrCheckoutRepository(testRepoUri, true); gitService.resetToOriginHead(solutionRepository); gitService.pullIgnoreConflicts(solutionRepository); diff --git a/src/main/java/de/tum/in/www1/artemis/service/programming/ProgrammingSubmissionService.java b/src/main/java/de/tum/in/www1/artemis/service/programming/ProgrammingSubmissionService.java index 93607ea81b1c..b42531af670c 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/programming/ProgrammingSubmissionService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/programming/ProgrammingSubmissionService.java @@ -357,7 +357,7 @@ public ProgrammingSubmission getOrCreateSubmissionWithLastCommitHashForParticipa private String getLastCommitHashForParticipation(ProgrammingExerciseParticipation participation) throws IllegalStateException { try { - return gitService.getLastCommitHash(participation.getVcsRepositoryUrl()).getName(); + return gitService.getLastCommitHash(participation.getVcsRepositoryUri()).getName(); } catch (EntityNotFoundException ex) { var message = "Last commit hash for participation " + participation.getId() + " could not be retrieved due to exception: " + ex.getMessage(); @@ -382,7 +382,7 @@ public ProgrammingSubmission createSolutionParticipationSubmissionWithTypeTest(L if (commitHash == null) { ProgrammingExercise programmingExercise = programmingExerciseRepository.findByIdWithTemplateAndSolutionParticipationElseThrow(programmingExerciseId); try { - commitHash = gitService.getLastCommitHash(programmingExercise.getVcsTestRepositoryUrl()).getName(); + commitHash = gitService.getLastCommitHash(programmingExercise.getVcsTestRepositoryUri()).getName(); } catch (EntityNotFoundException ex) { throw new IllegalStateException("Last commit hash for test repository of programming exercise with id " + programmingExercise.getId() + " could not be retrieved"); @@ -623,7 +623,7 @@ public void createInitialSubmissions(ProgrammingExercise programmingExercise) { */ private void createInitialSubmission(ProgrammingExercise programmingExercise, AbstractBaseProgrammingExerciseParticipation participation) { ProgrammingSubmission submission = (ProgrammingSubmission) submissionRepository.initializeSubmission(participation, programmingExercise, SubmissionType.INSTRUCTOR); - var latestHash = gitService.getLastCommitHash(participation.getVcsRepositoryUrl()); + var latestHash = gitService.getLastCommitHash(participation.getVcsRepositoryUri()); submission.setCommitHash(latestHash.getName()); submission.setSubmissionDate(ZonedDateTime.now()); submissionRepository.save(submission); diff --git a/src/main/java/de/tum/in/www1/artemis/service/scheduled/AutomaticProgrammingExerciseCleanupService.java b/src/main/java/de/tum/in/www1/artemis/service/scheduled/AutomaticProgrammingExerciseCleanupService.java index 8cde9c4f1daf..3e1ae98b682e 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/scheduled/AutomaticProgrammingExerciseCleanupService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/scheduled/AutomaticProgrammingExerciseCleanupService.java @@ -105,7 +105,7 @@ public void cleanupGitRepositoriesOnArtemisServer() { for (var programmingExercise : programmingExercises) { for (var studentParticipation : programmingExercise.getStudentParticipations()) { var programmingExerciseParticipation = (ProgrammingExerciseStudentParticipation) studentParticipation; - gitService.deleteLocalRepository(programmingExerciseParticipation.getVcsRepositoryUrl()); + gitService.deleteLocalRepository(programmingExerciseParticipation.getVcsRepositoryUri()); } } @@ -116,9 +116,9 @@ public void cleanupGitRepositoriesOnArtemisServer() { log.info("Found {} programming exercise to clean local template, test and solution: {}", programmingExercises.size(), programmingExercises.stream().map(ProgrammingExercise::getProjectKey).collect(Collectors.joining(", "))); for (var programmingExercise : programmingExercises) { - gitService.deleteLocalRepository(programmingExercise.getVcsTemplateRepositoryUrl()); - gitService.deleteLocalRepository(programmingExercise.getVcsSolutionRepositoryUrl()); - gitService.deleteLocalRepository(programmingExercise.getVcsTestRepositoryUrl()); + gitService.deleteLocalRepository(programmingExercise.getVcsTemplateRepositoryUri()); + gitService.deleteLocalRepository(programmingExercise.getVcsSolutionRepositoryUri()); + gitService.deleteLocalRepository(programmingExercise.getVcsTestRepositoryUri()); gitService.deleteLocalProgrammingExerciseReposFolder(programmingExercise); } } diff --git a/src/main/java/de/tum/in/www1/artemis/service/scheduled/ProgrammingExerciseScheduleService.java b/src/main/java/de/tum/in/www1/artemis/service/scheduled/ProgrammingExerciseScheduleService.java index 89b9dbeea633..a5fbd555dfd4 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/scheduled/ProgrammingExerciseScheduleService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/scheduled/ProgrammingExerciseScheduleService.java @@ -421,7 +421,7 @@ private Runnable combineTemplateCommitsForExercise(ProgrammingExercise exercise) try { ProgrammingExercise programmingExerciseWithTemplateParticipation = programmingExerciseRepository .findByIdWithTemplateAndSolutionParticipationElseThrow(exercise.getId()); - gitService.combineAllCommitsOfRepositoryIntoOne(programmingExerciseWithTemplateParticipation.getTemplateParticipation().getVcsRepositoryUrl()); + gitService.combineAllCommitsOfRepositoryIntoOne(programmingExerciseWithTemplateParticipation.getTemplateParticipation().getVcsRepositoryUri()); log.debug("Combined template repository commits of programming exercise {}.", programmingExerciseWithTemplateParticipation.getId()); } catch (GitAPIException e) { diff --git a/src/main/java/de/tum/in/www1/artemis/service/util/UrlUtils.java b/src/main/java/de/tum/in/www1/artemis/service/util/UrlUtils.java index 0671fac0ac80..7076e08d4bc6 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/util/UrlUtils.java +++ b/src/main/java/de/tum/in/www1/artemis/service/util/UrlUtils.java @@ -42,6 +42,6 @@ public static UriComponentsBuilder buildEndpoint(String baseUrl, List pa throw new IllegalArgumentException("Unable to build endpoint. Too many arguments! " + Arrays.toString(args)); } - return UriComponentsBuilder.fromHttpUrl(baseUrl).pathSegment(parsedSegments.toArray(new String[0])); + return UriComponentsBuilder.fromHttpUrl(baseUrl).pathSegment(parsedSegments.toArray(String[]::new)); } } diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/NotificationSettingsResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/NotificationSettingsResource.java index ffc4e35171a8..f42e6a9fac7b 100644 --- a/src/main/java/de/tum/in/www1/artemis/web/rest/NotificationSettingsResource.java +++ b/src/main/java/de/tum/in/www1/artemis/web/rest/NotificationSettingsResource.java @@ -90,7 +90,7 @@ public ResponseEntity saveNotificationSettingsForCurrentU if (resultAsList.isEmpty()) { throw new BadRequestAlertException("Error occurred during saving of Notification Settings", "NotificationSettings", "notificationSettingsEmptyAfterSave"); } - NotificationSetting[] resultAsArray = resultAsList.toArray(new NotificationSetting[0]); + NotificationSetting[] resultAsArray = resultAsList.toArray(NotificationSetting[]::new); return ResponseEntity.ok().headers(HeaderUtil.createEntityUpdateAlert(applicationName, true, "notificationSetting", "test")).body(resultAsArray); } diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/ParticipationResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/ParticipationResource.java index d0f41c78f81c..9d11fa47c33c 100644 --- a/src/main/java/de/tum/in/www1/artemis/web/rest/ParticipationResource.java +++ b/src/main/java/de/tum/in/www1/artemis/web/rest/ParticipationResource.java @@ -599,7 +599,7 @@ public ResponseEntity> getAllParticipationsForCourse( if (exercise instanceof ProgrammingExercise programmingExercise) { programmingExercise.setSolutionParticipation(null); programmingExercise.setTemplateParticipation(null); - programmingExercise.setTestRepositoryUrl(null); + programmingExercise.setTestRepositoryUri(null); programmingExercise.setShortName(null); programmingExercise.setPublishBuildPlanUrl(null); programmingExercise.setProgrammingLanguage(null); diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/ProgrammingExerciseExportImportResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/ProgrammingExerciseExportImportResource.java index ec3b44f36e89..26f06b0ff356 100644 --- a/src/main/java/de/tum/in/www1/artemis/web/rest/ProgrammingExerciseExportImportResource.java +++ b/src/main/java/de/tum/in/www1/artemis/web/rest/ProgrammingExerciseExportImportResource.java @@ -387,7 +387,7 @@ public ResponseEntity exportSubmissionsByStudentLogins(@PathVariable l List exportedStudentParticipations = new ArrayList<>(); for (StudentParticipation studentParticipation : programmingExercise.getStudentParticipations()) { ProgrammingExerciseStudentParticipation programmingStudentParticipation = (ProgrammingExerciseStudentParticipation) studentParticipation; - if (repositoryExportOptions.isExportAllParticipants() || (programmingStudentParticipation.getRepositoryUrl() != null && studentParticipation.getParticipant() != null + if (repositoryExportOptions.isExportAllParticipants() || (programmingStudentParticipation.getRepositoryUri() != null && studentParticipation.getParticipant() != null && participantIdentifierList.contains(studentParticipation.getParticipantIdentifier()))) { exportedStudentParticipations.add(programmingStudentParticipation); } diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/ProgrammingExerciseParticipationResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/ProgrammingExerciseParticipationResource.java index ecc18403be55..43b5d3662e40 100644 --- a/src/main/java/de/tum/in/www1/artemis/web/rest/ProgrammingExerciseParticipationResource.java +++ b/src/main/java/de/tum/in/www1/artemis/web/rest/ProgrammingExerciseParticipationResource.java @@ -14,7 +14,7 @@ import de.tum.in.www1.artemis.domain.ProgrammingExercise; import de.tum.in.www1.artemis.domain.ProgrammingSubmission; import de.tum.in.www1.artemis.domain.Result; -import de.tum.in.www1.artemis.domain.VcsRepositoryUrl; +import de.tum.in.www1.artemis.domain.VcsRepositoryUri; import de.tum.in.www1.artemis.domain.participation.ProgrammingExerciseStudentParticipation; import de.tum.in.www1.artemis.repository.ParticipationRepository; import de.tum.in.www1.artemis.repository.ProgrammingExerciseRepository; @@ -201,18 +201,18 @@ public ResponseEntity resetRepository(@PathVariable Long participationId, throw new BadRequestAlertException("Cannot reset repository in an exam", ENTITY_NAME, "noRepoResetInExam"); } - VcsRepositoryUrl sourceURL; + VcsRepositoryUri sourceURL; if (gradedParticipationId != null) { ProgrammingExerciseStudentParticipation gradedParticipation = programmingExerciseStudentParticipationRepository.findByIdElseThrow(gradedParticipationId); participationAuthCheckService.checkCanAccessParticipationElseThrow(gradedParticipation); - sourceURL = gradedParticipation.getVcsRepositoryUrl(); + sourceURL = gradedParticipation.getVcsRepositoryUri(); } else { - sourceURL = exercise.getVcsTemplateRepositoryUrl(); + sourceURL = exercise.getVcsTemplateRepositoryUri(); } - programmingExerciseParticipationService.resetRepository(participation.getVcsRepositoryUrl(), sourceURL); + programmingExerciseParticipationService.resetRepository(participation.getVcsRepositoryUri(), sourceURL); return ResponseEntity.ok().build(); } diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/ProgrammingExerciseResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/ProgrammingExerciseResource.java index 47ecb3699653..7aec667f119c 100644 --- a/src/main/java/de/tum/in/www1/artemis/web/rest/ProgrammingExerciseResource.java +++ b/src/main/java/de/tum/in/www1/artemis/web/rest/ProgrammingExerciseResource.java @@ -159,16 +159,16 @@ private void checkProgrammingExerciseForError(ProgrammingExercise exercise) { if (!continuousIntegration.checkIfBuildPlanExists(exercise.getProjectKey(), exercise.getTemplateBuildPlanId())) { throw new BadRequestAlertException("The Template Build Plan ID seems to be invalid.", "Exercise", ProgrammingExerciseResourceErrorKeys.INVALID_TEMPLATE_BUILD_PLAN_ID); } - if (exercise.getVcsTemplateRepositoryUrl() == null || !versionControl.repositoryUrlIsValid(exercise.getVcsTemplateRepositoryUrl())) { - throw new BadRequestAlertException("The Template Repository URL seems to be invalid.", "Exercise", + if (exercise.getVcsTemplateRepositoryUri() == null || !versionControl.repositoryUriIsValid(exercise.getVcsTemplateRepositoryUri())) { + throw new BadRequestAlertException("The Template Repository URI seems to be invalid.", "Exercise", ProgrammingExerciseResourceErrorKeys.INVALID_TEMPLATE_REPOSITORY_URL); } if (exercise.getSolutionBuildPlanId() != null && !continuousIntegration.checkIfBuildPlanExists(exercise.getProjectKey(), exercise.getSolutionBuildPlanId())) { throw new BadRequestAlertException("The Solution Build Plan ID seems to be invalid.", "Exercise", ProgrammingExerciseResourceErrorKeys.INVALID_SOLUTION_BUILD_PLAN_ID); } - var solutionRepositoryUrl = exercise.getVcsSolutionRepositoryUrl(); - if (solutionRepositoryUrl != null && !versionControl.repositoryUrlIsValid(solutionRepositoryUrl)) { - throw new BadRequestAlertException("The Solution Repository URL seems to be invalid.", "Exercise", + var solutionRepositoryUri = exercise.getVcsSolutionRepositoryUri(); + if (solutionRepositoryUri != null && !versionControl.repositoryUriIsValid(solutionRepositoryUri)) { + throw new BadRequestAlertException("The Solution Repository URI seems to be invalid.", "Exercise", ProgrammingExerciseResourceErrorKeys.INVALID_SOLUTION_REPOSITORY_URL); } @@ -501,8 +501,8 @@ public ResponseEntity combineTemplateRepositoryCommits(@PathVariable long var programmingExercise = programmingExerciseRepository.findByIdWithTemplateAndSolutionParticipationTeamAssignmentConfigCategoriesElseThrow(exerciseId); authCheckService.checkHasAtLeastRoleForExerciseElseThrow(Role.EDITOR, programmingExercise, null); try { - var exerciseRepoURL = programmingExercise.getVcsTemplateRepositoryUrl(); - gitService.combineAllCommitsOfRepositoryIntoOne(exerciseRepoURL); + var exerciseRepoUri = programmingExercise.getVcsTemplateRepositoryUri(); + gitService.combineAllCommitsOfRepositoryIntoOne(exerciseRepoUri); return new ResponseEntity<>(HttpStatus.OK); } catch (IllegalStateException | GitAPIException ex) { @@ -529,15 +529,15 @@ public ResponseEntity generateStructureOracleForExercise(@PathVariable l "This is a linked exercise and generating the structure oracle for this exercise is not possible.", "couldNotGenerateStructureOracle")).body(null); } - var solutionRepoURL = programmingExercise.getVcsSolutionRepositoryUrl(); - var exerciseRepoURL = programmingExercise.getVcsTemplateRepositoryUrl(); - var testRepoURL = programmingExercise.getVcsTestRepositoryUrl(); + var solutionRepoUri = programmingExercise.getVcsSolutionRepositoryUri(); + var exerciseRepoUri = programmingExercise.getVcsTemplateRepositoryUri(); + var testRepoUri = programmingExercise.getVcsTestRepositoryUri(); try { String testsPath = Path.of("test", programmingExercise.getPackageFolderName()).toString(); // Atm we only have one folder that can have structural tests, but this could change. testsPath = programmingExercise.hasSequentialTestRuns() ? Path.of("structural", testsPath).toString() : testsPath; - boolean didGenerateOracle = programmingExerciseService.generateStructureOracleFile(solutionRepoURL, exerciseRepoURL, testRepoURL, testsPath, user); + boolean didGenerateOracle = programmingExerciseService.generateStructureOracleFile(solutionRepoUri, exerciseRepoUri, testRepoUri, testsPath, user); if (didGenerateOracle) { HttpHeaders responseHeaders = new HttpHeaders(); diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/repository/RepositoryProgrammingExerciseParticipationResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/repository/RepositoryProgrammingExerciseParticipationResource.java index 2ca65ee42a56..99285c5814e4 100644 --- a/src/main/java/de/tum/in/www1/artemis/web/rest/repository/RepositoryProgrammingExerciseParticipationResource.java +++ b/src/main/java/de/tum/in/www1/artemis/web/rest/repository/RepositoryProgrammingExerciseParticipationResource.java @@ -99,22 +99,22 @@ Repository getRepository(Long participationId, RepositoryActionType repositoryAc throw new AccessForbiddenException(e); } - var repositoryUrl = programmingParticipation.getVcsRepositoryUrl(); + var repositoryUri = programmingParticipation.getVcsRepositoryUri(); // This check reduces the amount of REST-calls that retrieve the default branch of a repository. // Retrieving the default branch is not necessary if the repository is already cached. - if (gitService.isRepositoryCached(repositoryUrl)) { - return gitService.getOrCheckoutRepository(repositoryUrl, pullOnGet); + if (gitService.isRepositoryCached(repositoryUri)) { + return gitService.getOrCheckoutRepository(repositoryUri, pullOnGet); } else { String branch = versionControlService.orElseThrow().getOrRetrieveBranchOfParticipation(programmingParticipation); - return gitService.getOrCheckoutRepository(repositoryUrl, pullOnGet, branch); + return gitService.getOrCheckoutRepository(repositoryUri, pullOnGet, branch); } } @Override - VcsRepositoryUrl getRepositoryUrl(Long participationId) throws IllegalArgumentException { - return getProgrammingExerciseParticipation(participationId).getVcsRepositoryUrl(); + VcsRepositoryUri getRepositoryUri(Long participationId) throws IllegalArgumentException { + return getProgrammingExerciseParticipation(participationId).getVcsRepositoryUri(); } /** @@ -189,7 +189,7 @@ public ResponseEntity> getFilesAtCommit(@PathVariable long p throw new AccessForbiddenException(e); } return executeAndCheckForExceptions(() -> { - Repository repository = gitService.checkoutRepositoryAtCommit(getRepositoryUrl(participationId), commitId, true); + Repository repository = gitService.checkoutRepositoryAtCommit(getRepositoryUri(participationId), commitId, true); Map filesWithContent = super.repositoryService.getFilesWithContent(repository); gitService.switchBackToDefaultBranchHead(repository); return new ResponseEntity<>(filesWithContent, HttpStatus.OK); diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/repository/RepositoryResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/repository/RepositoryResource.java index d11adb9f41bb..062fed7d098d 100644 --- a/src/main/java/de/tum/in/www1/artemis/web/rest/repository/RepositoryResource.java +++ b/src/main/java/de/tum/in/www1/artemis/web/rest/repository/RepositoryResource.java @@ -99,9 +99,9 @@ public RepositoryResource(ProfileService profileService, UserRepository userRepo * Get the url for a repository. * * @param domainId that serves as an abstract identifier for retrieving the repository. - * @return the repositoryUrl. + * @return the repositoryUri. */ - abstract VcsRepositoryUrl getRepositoryUrl(Long domainId); + abstract VcsRepositoryUri getRepositoryUri(Long domainId); /** * Check if the current user can access the given repository. @@ -301,18 +301,18 @@ public ResponseEntity getStatus(Long domainId) throws GitAP } RepositoryStatusDTOType repositoryStatus; - VcsRepositoryUrl repositoryUrl = getRepositoryUrl(domainId); + VcsRepositoryUri repositoryUri = getRepositoryUri(domainId); try { boolean isClean; // This check reduces the amount of REST-calls that retrieve the default branch of a repository. // Retrieving the default branch is not necessary if the repository is already cached. - if (gitService.isRepositoryCached(repositoryUrl)) { - isClean = repositoryService.isClean(repositoryUrl); + if (gitService.isRepositoryCached(repositoryUri)) { + isClean = repositoryService.isClean(repositoryUri); } else { String branch = getOrRetrieveBranchOfDomainObject(domainId); - isClean = repositoryService.isClean(repositoryUrl, branch); + isClean = repositoryService.isClean(repositoryUri, branch); } repositoryStatus = isClean ? CLEAN : UNCOMMITTED_CHANGES; } diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/repository/TestRepositoryResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/repository/TestRepositoryResource.java index bad42af629d5..2eea6eda53f4 100644 --- a/src/main/java/de/tum/in/www1/artemis/web/rest/repository/TestRepositoryResource.java +++ b/src/main/java/de/tum/in/www1/artemis/web/rest/repository/TestRepositoryResource.java @@ -52,14 +52,14 @@ Repository getRepository(Long exerciseId, RepositoryActionType repositoryActionT final var exercise = programmingExerciseRepository.findByIdWithTemplateAndSolutionParticipationElseThrow(exerciseId); User user = userRepository.getUserWithGroupsAndAuthorities(); repositoryAccessService.checkAccessTestOrAuxRepositoryElseThrow(false, exercise, user, "test"); - final var repoUrl = exercise.getVcsTestRepositoryUrl(); - return gitService.getOrCheckoutRepository(repoUrl, pullOnGet); + final var repoUri = exercise.getVcsTestRepositoryUri(); + return gitService.getOrCheckoutRepository(repoUri, pullOnGet); } @Override - VcsRepositoryUrl getRepositoryUrl(Long exerciseId) { + VcsRepositoryUri getRepositoryUri(Long exerciseId) { ProgrammingExercise exercise = programmingExerciseRepository.findByIdWithTemplateAndSolutionParticipationElseThrow(exerciseId); - return exercise.getVcsTestRepositoryUrl(); + return exercise.getVcsTestRepositoryUri(); } @Override @@ -179,7 +179,7 @@ public ResponseEntity> updateTestFiles(@PathVariable("exerci Repository repository; try { repositoryAccessService.checkAccessTestOrAuxRepositoryElseThrow(true, exercise, userRepository.getUserWithGroupsAndAuthorities(principal.getName()), "test"); - repository = gitService.getOrCheckoutRepository(exercise.getVcsTestRepositoryUrl(), true); + repository = gitService.getOrCheckoutRepository(exercise.getVcsTestRepositoryUri(), true); } catch (AccessForbiddenException e) { FileSubmissionError error = new FileSubmissionError(exerciseId, "noPermissions"); diff --git a/src/main/webapp/app/assessment/assessment-instructions/assessment-instructions/assessment-instructions.component.html b/src/main/webapp/app/assessment/assessment-instructions/assessment-instructions/assessment-instructions.component.html index 568c7aa97858..71388f4275f6 100644 --- a/src/main/webapp/app/assessment/assessment-instructions/assessment-instructions/assessment-instructions.component.html +++ b/src/main/webapp/app/assessment/assessment-instructions/assessment-instructions/assessment-instructions.component.html @@ -27,7 +27,7 @@

{{ exercise.title }}

@switch (exercise.type) { @case (ExerciseType.PROGRAMMING) { diff --git a/src/main/webapp/app/entities/participation/programming-exercise-student-participation.model.ts b/src/main/webapp/app/entities/participation/programming-exercise-student-participation.model.ts index 7e9d6b61049e..6a6700137ece 100644 --- a/src/main/webapp/app/entities/participation/programming-exercise-student-participation.model.ts +++ b/src/main/webapp/app/entities/participation/programming-exercise-student-participation.model.ts @@ -2,14 +2,14 @@ import { StudentParticipation } from 'app/entities/participation/student-partici import { ParticipationType } from 'app/entities/participation/participation.model'; export class ProgrammingExerciseStudentParticipation extends StudentParticipation { - public repositoryUrl?: string; + public repositoryUri?: string; public buildPlanId?: string; public branch?: string; public locked?: boolean; // helper attribute public buildPlanUrl?: string; - public userIndependentRepositoryUrl?: string; + public userIndependentRepositoryUri?: string; constructor() { super(ParticipationType.PROGRAMMING); diff --git a/src/main/webapp/app/entities/participation/solution-programming-exercise-participation.model.ts b/src/main/webapp/app/entities/participation/solution-programming-exercise-participation.model.ts index d0715826f58f..d9db5fc2b9d3 100644 --- a/src/main/webapp/app/entities/participation/solution-programming-exercise-participation.model.ts +++ b/src/main/webapp/app/entities/participation/solution-programming-exercise-participation.model.ts @@ -3,7 +3,7 @@ import { ProgrammingExercise } from 'app/entities/programming-exercise.model'; export class SolutionProgrammingExerciseParticipation extends Participation { public programmingExercise?: ProgrammingExercise; - public repositoryUrl?: string; + public repositoryUri?: string; public buildPlanId?: string; public buildPlanUrl?: string; diff --git a/src/main/webapp/app/entities/participation/template-programming-exercise-participation.model.ts b/src/main/webapp/app/entities/participation/template-programming-exercise-participation.model.ts index 6000c50275d1..248a30b3e3f4 100644 --- a/src/main/webapp/app/entities/participation/template-programming-exercise-participation.model.ts +++ b/src/main/webapp/app/entities/participation/template-programming-exercise-participation.model.ts @@ -3,7 +3,7 @@ import { ProgrammingExercise } from 'app/entities/programming-exercise.model'; export class TemplateProgrammingExerciseParticipation extends Participation { public programmingExercise?: ProgrammingExercise; - public repositoryUrl?: string; + public repositoryUri?: string; public buildPlanId?: string; public buildPlanUrl?: string; diff --git a/src/main/webapp/app/entities/programming-exercise-auxiliary-repository-model.ts b/src/main/webapp/app/entities/programming-exercise-auxiliary-repository-model.ts index 574f534d9213..3c77e7675bb1 100644 --- a/src/main/webapp/app/entities/programming-exercise-auxiliary-repository-model.ts +++ b/src/main/webapp/app/entities/programming-exercise-auxiliary-repository-model.ts @@ -4,6 +4,6 @@ export class AuxiliaryRepository implements BaseEntity { public id?: number; public name?: string; public checkoutDirectory?: string; - public repositoryUrl?: string; + public repositoryUri?: string; public description?: string; } diff --git a/src/main/webapp/app/entities/programming-exercise.model.ts b/src/main/webapp/app/entities/programming-exercise.model.ts index a315a126516c..3ec9138f12be 100644 --- a/src/main/webapp/app/entities/programming-exercise.model.ts +++ b/src/main/webapp/app/entities/programming-exercise.model.ts @@ -85,7 +85,7 @@ export class ProgrammingExercise extends Exercise { public projectKey?: string; public templateParticipation?: TemplateProgrammingExerciseParticipation; public solutionParticipation?: SolutionProgrammingExerciseParticipation; - public testRepositoryUrl?: string; + public testRepositoryUri?: string; public publishBuildPlanUrl?: boolean; public customizeBuildPlanWithAeolus?: boolean; public allowOnlineEditor?: boolean; diff --git a/src/main/webapp/app/exam/manage/exercise-groups/exercise-groups.component.html b/src/main/webapp/app/exam/manage/exercise-groups/exercise-groups.component.html index 3f9858ee3efa..ecd41d3fc144 100644 --- a/src/main/webapp/app/exam/manage/exercise-groups/exercise-groups.component.html +++ b/src/main/webapp/app/exam/manage/exercise-groups/exercise-groups.component.html @@ -294,7 +294,7 @@
{{ exerciseGroup.title }}
@if (exercise.type === exerciseType.PROGRAMMING) { - + } diff --git a/src/main/webapp/app/exam/manage/exercise-groups/programming-exercise-cell/programming-exercise-group-cell.component.html b/src/main/webapp/app/exam/manage/exercise-groups/programming-exercise-cell/programming-exercise-group-cell.component.html index 88dacd59bad2..05dafc5135d3 100644 --- a/src/main/webapp/app/exam/manage/exercise-groups/programming-exercise-cell/programming-exercise-group-cell.component.html +++ b/src/main/webapp/app/exam/manage/exercise-groups/programming-exercise-cell/programming-exercise-group-cell.component.html @@ -4,15 +4,15 @@ {{ programmingExercise.shortName || '' }}
} - @if (displayRepositoryUrl) { + @if (displayRepositoryUri) {
- @if (programmingExercise.templateParticipation?.repositoryUrl) { + @if (programmingExercise.templateParticipation?.repositoryUri) { @if (!localVCEnabled) { - Template + Template } @else { Template @@ -27,10 +27,10 @@ }
- @if (programmingExercise.solutionParticipation?.repositoryUrl) { + @if (programmingExercise.solutionParticipation?.repositoryUri) { @if (!localVCEnabled) { - Solution + Solution } @else { Solution @@ -45,10 +45,10 @@ }
- @if (programmingExercise.testRepositoryUrl) { + @if (programmingExercise.testRepositoryUri) { @if (!localVCEnabled) { - Test + Test } @else { Test diff --git a/src/main/webapp/app/exam/manage/exercise-groups/programming-exercise-cell/programming-exercise-group-cell.component.ts b/src/main/webapp/app/exam/manage/exercise-groups/programming-exercise-cell/programming-exercise-group-cell.component.ts index f62ffbccff34..949ab1482efe 100644 --- a/src/main/webapp/app/exam/manage/exercise-groups/programming-exercise-cell/programming-exercise-group-cell.component.ts +++ b/src/main/webapp/app/exam/manage/exercise-groups/programming-exercise-cell/programming-exercise-group-cell.component.ts @@ -26,7 +26,7 @@ export class ProgrammingExerciseGroupCellComponent implements OnInit { @Input() displayShortName = false; @Input() - displayRepositoryUrl = false; + displayRepositoryUri = false; @Input() displayTemplateUrls = false; @Input() diff --git a/src/main/webapp/app/exam/participate/summary/exercises/programming-exam-summary/programming-exam-summary.component.html b/src/main/webapp/app/exam/participate/summary/exercises/programming-exam-summary/programming-exam-summary.component.html index 093c516afa26..b1ee2eebbcf5 100644 --- a/src/main/webapp/app/exam/participate/summary/exercises/programming-exam-summary/programming-exam-summary.component.html +++ b/src/main/webapp/app/exam/participate/summary/exercises/programming-exam-summary/programming-exam-summary.component.html @@ -4,7 +4,7 @@
{{ 'artemisApp.exam.examSummary.yourSubmission' | artemisTranslate }}
- +
{{ 'artemisApp.exam.examSummary.submissionLinkedToCommit' | artemisTranslate }} diff --git a/src/main/webapp/app/exercises/programming/assess/code-editor-tutor-assessment-container.component.html b/src/main/webapp/app/exercises/programming/assess/code-editor-tutor-assessment-container.component.html index bc68d53a8697..1dd2b0990401 100644 --- a/src/main/webapp/app/exercises/programming/assess/code-editor-tutor-assessment-container.component.html +++ b/src/main/webapp/app/exercises/programming/assess/code-editor-tutor-assessment-container.component.html @@ -107,7 +107,7 @@ @if (!localVCEnabled) { {{ selectedRepository }}
-
- -
-
- @if (isBonusGradingKeyDisplayed) { -
- -
- -
-
}
+
+ + + @if (gradingScaleExists) { + +
+   + {{ 'artemisApp.exam.examSummary.gradeKey' | artemisTranslate }} +
+ +
+ +
+
+ + @if (isBonusGradingKeyDisplayed) { + +
+   + {{ 'artemisApp.exam.examSummary.bonusGradeKey' | artemisTranslate }} +
+ +
+ +
+
} -
+ }
} diff --git a/src/main/webapp/app/exam/participate/summary/result-overview/exam-result-overview.component.scss b/src/main/webapp/app/exam/participate/summary/result-overview/exam-result-overview.component.scss index f053df75c6b5..53dd74ed2c52 100644 --- a/src/main/webapp/app/exam/participate/summary/result-overview/exam-result-overview.component.scss +++ b/src/main/webapp/app/exam/participate/summary/result-overview/exam-result-overview.component.scss @@ -30,19 +30,6 @@ border-top-width: 2px; } -.chevron-position { - display: inline-block; - vertical-align: middle; -} - -.rotate-icon { - transition: transform 0.3s ease; -} - -.rotate-icon.rotated { - transform: rotate(90deg); -} - .icon-container { display: inline-block; vertical-align: middle; @@ -55,3 +42,7 @@ width: 24px; height: 24px; } + +.grade-results-table-container { + width: fit-content; +} diff --git a/src/main/webapp/app/exam/participate/summary/result-overview/exam-result-overview.component.ts b/src/main/webapp/app/exam/participate/summary/result-overview/exam-result-overview.component.ts index 4f8d10913e43..9dd49d98af7c 100644 --- a/src/main/webapp/app/exam/participate/summary/result-overview/exam-result-overview.component.ts +++ b/src/main/webapp/app/exam/participate/summary/result-overview/exam-result-overview.component.ts @@ -18,6 +18,8 @@ type ExerciseInfo = { colorClass?: string; }; +type ResultOverviewSection = 'grading-table' | 'grading-key' | 'bonus-grading-key'; + @Component({ selector: 'jhi-exam-result-overview', styleUrls: ['./exam-result-overview.component.scss'], @@ -61,6 +63,12 @@ export class ExamResultOverviewComponent implements OnInit, OnChanges { */ showResultOverview = false; + isCollapsed: Record = { + 'grading-table': false, + 'grading-key': true, + 'bonus-grading-key': true, + }; + constructor( private serverDateService: ArtemisServerDateService, public exerciseService: ExerciseService, @@ -89,11 +97,55 @@ export class ExamResultOverviewComponent implements OnInit, OnChanges { this.maxPoints = this.studentExamWithGrade?.maxPoints ?? 0; this.isBonusGradingKeyDisplayed = this.studentExamWithGrade.studentResult.gradeWithBonus?.bonusGrade != undefined; - this.overallAchievedPoints = this.studentExamWithGrade?.studentResult.overallPointsAchieved ?? 0; - this.overallAchievedPercentageRoundedByCourseSettings = roundScorePercentSpecifiedByCourseSettings( - (this.studentExamWithGrade.studentResult.overallScoreAchieved ?? 0) / 100, - this.studentExamWithGrade.studentExam?.exam?.course, - ); + this.overallAchievedPoints = this.getOverallAchievedPoints(); + this.overallAchievedPercentageRoundedByCourseSettings = this.getOverallAchievedPercentageRoundedByCourseSettings(); + } + + /** + * used as fallback if not pre-calculated by the server + */ + private sumExerciseScores() { + return (this.studentExamWithGrade.studentExam?.exercises ?? []).reduce((exerciseScoreSum, exercise) => { + const achievedPoints = this.studentExamWithGrade?.achievedPointsPerExercise?.[exercise.id!] ?? 0; + return exerciseScoreSum + achievedPoints; + }, 0); + } + + private getOverallAchievedPoints() { + const overallAchievedPoints = this.studentExamWithGrade?.studentResult.overallPointsAchieved; + if (overallAchievedPoints === undefined || overallAchievedPoints === 0) { + return this.sumExerciseScores(); + } + + return overallAchievedPoints; + } + + private getOverallAchievedPercentageRoundedByCourseSettings() { + let overallScoreAchieved = this.studentExamWithGrade.studentResult.overallScoreAchieved; + if (overallScoreAchieved === undefined || overallScoreAchieved === 0) { + overallScoreAchieved = this.summedAchievedExerciseScorePercentage(); + } + + return roundScorePercentSpecifiedByCourseSettings(overallScoreAchieved / 100, this.studentExamWithGrade.studentExam?.exam?.course); + } + + /** + * used as fallback if not pre-calculated by the server + */ + private summedAchievedExerciseScorePercentage() { + let summedPercentages = 0; + let numberOfExercises = 0; + + Object.entries(this.exerciseInfos).forEach(([, exerciseInfo]) => { + summedPercentages += exerciseInfo.achievedPercentage ?? 0; + numberOfExercises++; + }); + + if (numberOfExercises === 0) { + return 0; + } + + return summedPercentages / numberOfExercises; } /** @@ -175,4 +227,8 @@ export class ExamResultOverviewComponent implements OnInit, OnChanges { toggleBonusGradingKey(): void { this.isBonusGradingKeyCollapsed = !this.isBonusGradingKeyCollapsed; } + + toggleCollapse(resultOverviewSection: ResultOverviewSection) { + return () => (this.isCollapsed[resultOverviewSection] = !this.isCollapsed[resultOverviewSection]); + } } diff --git a/src/main/webapp/app/grading-system/grading-key-overview/grading-key/grading-key-table.component.html b/src/main/webapp/app/grading-system/grading-key-overview/grading-key/grading-key-table.component.html index e7f43780996d..907254c1a3f3 100644 --- a/src/main/webapp/app/grading-system/grading-key-overview/grading-key/grading-key-table.component.html +++ b/src/main/webapp/app/grading-system/grading-key-overview/grading-key/grading-key-table.component.html @@ -3,7 +3,7 @@
{{ 'artemisApp.gradingSystem.overview.info' | artemisTranslate }}
}
- +
diff --git a/src/main/webapp/i18n/de/exam.json b/src/main/webapp/i18n/de/exam.json index 5f67faae9e4d..e0330230538e 100644 --- a/src/main/webapp/i18n/de/exam.json +++ b/src/main/webapp/i18n/de/exam.json @@ -67,6 +67,7 @@ "backToOverview": "Zurück zur Übersicht", "examResults": "Prüfungsergebnisse", "generalInformation": "Allgemeine Informationen", + "youAreViewingAnUnsubmittedExam": "Du betrachtest eine nicht eingereichte Klausur", "exportPDF": "Als PDF exportieren", "noSubmissionFound": "Du hast keine Lösung für diese Aufgabe abgegeben.", "submissionLinkedToCommit": "Die abgegebene Lösung ist mit folgendem Commit verknüpft: ", @@ -103,8 +104,10 @@ "interval": "Intervall (%)", "intervalPoints": "Intervall (Punkte)", "gradeKeyButton": "Notenschlüssel ansehen", + "gradeKey": "Notenschlüssel", "gradeKeyButtonBonus": "Bonusregeln ansehen", - "bonusGradeKeyButton": "Bonusnotenschlüssel ansehen" + "bonusGradeKey": "Bonusnotenschlüssel", + "resultTable": "Ergebnistabelle" }, "title": "Klausur", "startExam": "Starten", diff --git a/src/main/webapp/i18n/en/exam.json b/src/main/webapp/i18n/en/exam.json index 8a120276725f..2e6646d43388 100644 --- a/src/main/webapp/i18n/en/exam.json +++ b/src/main/webapp/i18n/en/exam.json @@ -67,6 +67,7 @@ "backToOverview": "Back to overview", "examResults": "Exam Results", "generalInformation": "General Information", + "youAreViewingAnUnsubmittedExam": "You are viewing an unsubmitted exam", "exportPDF": "Export PDF", "noSubmissionFound": "You didn't submit any solution for this exercise.", "submissionLinkedToCommit": "The submission is linked to commit", @@ -103,8 +104,10 @@ "interval": "Interval (%)", "intervalPoints": "Interval (Points)", "gradeKeyButton": "View Grading Key", + "gradeKey": "Grading Key", "gradeKeyButtonBonus": "View Bonus Rules", - "bonusGradeKeyButton": "View Bonus Grading Key" + "bonusGradeKey": "Bonus Grading Key", + "resultTable": "Result Table" }, "title": "Exam", "startExam": "Start", diff --git a/src/test/cypress/support/pageobjects/assessment/StudentAssessmentPage.ts b/src/test/cypress/support/pageobjects/assessment/StudentAssessmentPage.ts index 70404d537bfe..43a6bc0e2388 100644 --- a/src/test/cypress/support/pageobjects/assessment/StudentAssessmentPage.ts +++ b/src/test/cypress/support/pageobjects/assessment/StudentAssessmentPage.ts @@ -3,7 +3,7 @@ */ export class StudentAssessmentPage { startComplaint() { - cy.get('#complain').click(); + cy.get('#complain', { timeout: 30000 }).click(); } enterComplaint(text: string) { diff --git a/src/test/java/de/tum/in/www1/artemis/service/exam/ExamServiceTest.java b/src/test/java/de/tum/in/www1/artemis/service/exam/ExamServiceTest.java index be7ce89c2f3a..89438e376fc7 100644 --- a/src/test/java/de/tum/in/www1/artemis/service/exam/ExamServiceTest.java +++ b/src/test/java/de/tum/in/www1/artemis/service/exam/ExamServiceTest.java @@ -218,7 +218,7 @@ void getChecklistStatsEmpty() { } @Nested - class GetStudentExamGradesForSummaryAsStudentTest { + class GetStudentExamGradesForSummaryTest { private static final int NUMBER_OF_STUDENTS = 1; @@ -230,8 +230,6 @@ class GetStudentExamGradesForSummaryAsStudentTest { private StudentExam studentExam; - private boolean isTestRun; - @BeforeEach void initializeTest() { userUtilService.addUsers(TEST_PREFIX, NUMBER_OF_STUDENTS, 0, 0, NUMBER_OF_INSTRUCTORS); @@ -247,9 +245,9 @@ void initializeTest() { @WithMockUser(username = "student1", roles = "STUDENT") void testThrowsExceptionIfNotSubmitted() { studentExam.setSubmitted(false); - isTestRun = false; + boolean isInstructor = false; - assertThatExceptionOfType(AccessForbiddenException.class).isThrownBy(() -> examService.getStudentExamGradesForSummaryAsStudent(student1, studentExam, isTestRun)) + assertThatExceptionOfType(AccessForbiddenException.class).isThrownBy(() -> examService.getStudentExamGradesForSummary(student1, studentExam, isInstructor)) .withMessage("You are not allowed to access the grade summary of a student exam which was NOT submitted!"); } @@ -257,24 +255,24 @@ void testThrowsExceptionIfNotSubmitted() { @WithMockUser(username = "student1", roles = "STUDENT") void testThrowsExceptionIfNotPublished() { studentExam.setSubmitted(true); - isTestRun = false; studentExam.getExam().setPublishResultsDate(ZonedDateTime.now().plusDays(5)); + boolean isInstructor = false; - assertThatExceptionOfType(AccessForbiddenException.class).isThrownBy(() -> examService.getStudentExamGradesForSummaryAsStudent(student1, studentExam, isTestRun)) + assertThatExceptionOfType(AccessForbiddenException.class).isThrownBy(() -> examService.getStudentExamGradesForSummary(student1, studentExam, isInstructor)) .withMessage("You are not allowed to access the grade summary of a student exam before the release date of results"); } @Test @WithMockUser(username = "instructor1", roles = "INSTRUCTOR") - void testDoesNotThrowExceptionForTestRuns() { + void testDoesNotThrowExceptionForInstructors() { studentExam.setSubmitted(false); studentExam.getExam().setPublishResultsDate(ZonedDateTime.now().plusDays(5)); + studentExam.getExam().setTestExam(true); // test runs are an edge case where instructors want to have access before the publishing date of results studentExam.setUser(instructor1); - isTestRun = true; + boolean isInstructor = true; - examService.getStudentExamGradesForSummaryAsStudent(instructor1, studentExam, isTestRun); + examService.getStudentExamGradesForSummary(student1, studentExam, isInstructor); } - } private Exam createExam(int numberOfExercisesInExam, Long id, Integer maxPoints) { diff --git a/src/test/javascript/spec/component/exam/participate/summary/collapsible-card.component.spec.ts b/src/test/javascript/spec/component/exam/participate/summary/collapsible-card.component.spec.ts new file mode 100644 index 000000000000..b1db2cae3153 --- /dev/null +++ b/src/test/javascript/spec/component/exam/participate/summary/collapsible-card.component.spec.ts @@ -0,0 +1,35 @@ +import { ComponentFixture, TestBed, fakeAsync } from '@angular/core/testing'; +import { CollapsibleCardComponent } from 'app/exam/participate/summary/collapsible-card.component'; +import { By } from '@angular/platform-browser'; + +let fixture: ComponentFixture; +let component: CollapsibleCardComponent; + +describe('CollapsibleCardComponent', () => { + beforeEach(() => { + return TestBed.configureTestingModule({}) + .compileComponents() + .then(() => { + fixture = TestBed.createComponent(CollapsibleCardComponent); + component = fixture.componentInstance; + + component.toggleCollapse = () => {}; + component.isCardContentCollapsed = false; + }); + }); + + it('should collapse and expand exercise when collapse button is clicked', fakeAsync(() => { + const toggleCollapseSpy = jest.spyOn(component, 'toggleCollapse').mockImplementation(() => {}); + + fixture.detectChanges(); + const toggleCollapseHeader = fixture.debugElement.query(By.css('.card-header')); + + expect(toggleCollapseHeader).not.toBeNull(); + + toggleCollapseHeader.nativeElement.click(); + expect(toggleCollapseSpy).toHaveBeenCalledOnce(); + + toggleCollapseHeader.nativeElement.click(); + expect(toggleCollapseSpy).toHaveBeenCalledTimes(2); + })); +}); diff --git a/src/test/javascript/spec/component/exam/participate/summary/exam-result-summary.component.spec.ts b/src/test/javascript/spec/component/exam/participate/summary/exam-result-summary.component.spec.ts index 62ae4fc3b049..d0935f14d35a 100644 --- a/src/test/javascript/spec/component/exam/participate/summary/exam-result-summary.component.spec.ts +++ b/src/test/javascript/spec/component/exam/participate/summary/exam-result-summary.component.spec.ts @@ -56,6 +56,7 @@ import { AlertService } from 'app/core/util/alert.service'; import { ProgrammingExerciseExampleSolutionRepoDownloadComponent } from 'app/exercises/programming/shared/actions/programming-exercise-example-solution-repo-download.component'; import * as Utils from 'app/shared/util/utils'; import * as ExamUtils from 'app/exam/participate/exam.utils'; +import { CollapsibleCardComponent } from 'app/exam/participate/summary/collapsible-card.component'; let fixture: ComponentFixture; let component: ExamResultSummaryComponent; @@ -111,10 +112,30 @@ const quizParticipation = { id: 2, student: user, submissions: [quizSubmission] const modelingParticipation = { id: 3, student: user, submissions: [modelingSubmission] } as StudentParticipation; const programmingParticipation = { id: 4, student: user, submissions: [programmingSubmission] } as StudentParticipation; -const textExercise = { id: 1, type: ExerciseType.TEXT, studentParticipations: [textParticipation], exerciseGroup } as TextExercise; -const quizExercise = { id: 2, type: ExerciseType.QUIZ, studentParticipations: [quizParticipation], exerciseGroup } as QuizExercise; -const modelingExercise = { id: 3, type: ExerciseType.MODELING, studentParticipations: [modelingParticipation], exerciseGroup } as ModelingExercise; -const programmingExercise = { id: 4, type: ExerciseType.PROGRAMMING, studentParticipations: [programmingParticipation], exerciseGroup } as ProgrammingExercise; +const textExercise = { + id: 1, + type: ExerciseType.TEXT, + studentParticipations: [textParticipation], + exerciseGroup, +} as TextExercise; +const quizExercise = { + id: 2, + type: ExerciseType.QUIZ, + studentParticipations: [quizParticipation], + exerciseGroup, +} as QuizExercise; +const modelingExercise = { + id: 3, + type: ExerciseType.MODELING, + studentParticipations: [modelingParticipation], + exerciseGroup, +} as ModelingExercise; +const programmingExercise = { + id: 4, + type: ExerciseType.PROGRAMMING, + studentParticipations: [programmingParticipation], + exerciseGroup, +} as ProgrammingExercise; const exercises = [textExercise, quizExercise, modelingExercise, programmingExercise]; const studentExam = { @@ -131,7 +152,12 @@ const studentExamForTestExam = { exercises, } as StudentExam; -const textExerciseResult = { exerciseId: textExercise.id, achievedScore: 60, achievedPoints: 6, maxScore: textExercise.maxPoints } as ExerciseResult; +const textExerciseResult = { + exerciseId: textExercise.id, + achievedScore: 60, + achievedPoints: 6, + maxScore: textExercise.maxPoints, +} as ExerciseResult; const gradeInfo: StudentExamWithGradeDTO = { maxPoints: 100, @@ -169,6 +195,7 @@ function sharedSetup(url: string[]) { MockPipe(HtmlForMarkdownPipe), MockComponent(IncludedInScoreBadgeComponent), MockComponent(ProgrammingExerciseExampleSolutionRepoDownloadComponent), + MockComponent(CollapsibleCardComponent), ], providers: [ { diff --git a/src/test/javascript/spec/component/exam/participate/summary/exercises/header/exam-result-summary-exercise-card-header.component.spec.ts b/src/test/javascript/spec/component/exam/participate/summary/exercises/header/exam-result-summary-exercise-card-header.component.spec.ts index 12b0b9310ae5..05f9983f229c 100644 --- a/src/test/javascript/spec/component/exam/participate/summary/exercises/header/exam-result-summary-exercise-card-header.component.spec.ts +++ b/src/test/javascript/spec/component/exam/participate/summary/exercises/header/exam-result-summary-exercise-card-header.component.spec.ts @@ -1,4 +1,4 @@ -import { ComponentFixture, TestBed, fakeAsync } from '@angular/core/testing'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { FaIconComponent } from '@fortawesome/angular-fontawesome'; import { User } from 'app/core/user/user.model'; @@ -55,21 +55,6 @@ describe('ExamResultSummaryExerciseCardHeaderComponent', () => { jest.restoreAllMocks(); }); - it('should collapse and expand exercise when collapse button is clicked', fakeAsync(() => { - fixture.detectChanges(); - const toggleCollapseExerciseButtonFour = fixture.debugElement.query(By.css('#toggleCollapseExerciseButton-4')); - - expect(toggleCollapseExerciseButtonFour).not.toBeNull(); - - toggleCollapseExerciseButtonFour.nativeElement.click(); - - expect(component.exerciseInfo?.isCollapsed).toBeTrue(); - - toggleCollapseExerciseButtonFour.nativeElement.click(); - - expect(component.exerciseInfo?.isCollapsed).toBeFalse(); - })); - it.each([ [{}, false], [{ studentParticipations: null }, false], diff --git a/src/test/javascript/spec/component/exam/participate/summary/result-overview/exam-result-overview.component.spec.ts b/src/test/javascript/spec/component/exam/participate/summary/result-overview/exam-result-overview.component.spec.ts index 19a11917af28..df597333fc63 100644 --- a/src/test/javascript/spec/component/exam/participate/summary/result-overview/exam-result-overview.component.spec.ts +++ b/src/test/javascript/spec/component/exam/participate/summary/result-overview/exam-result-overview.component.spec.ts @@ -21,6 +21,7 @@ import { Course } from 'app/entities/course.model'; import { FaIconComponent } from '@fortawesome/angular-fontawesome'; import { ExerciseResult, StudentExamWithGradeDTO } from 'app/exam/exam-scores/exam-score-dtos.model'; import { GradingKeyTableComponent } from 'app/grading-system/grading-key-overview/grading-key/grading-key-table.component'; +import { CollapsibleCardComponent } from 'app/exam/participate/summary/collapsible-card.component'; let fixture: ComponentFixture; let component: ExamResultOverviewComponent; @@ -117,13 +118,24 @@ const programmingExerciseTwo = { } as ProgrammingExercise; const exercises = [textExercise, quizExercise, modelingExercise, programmingExercise, programmingExerciseTwo, notIncludedTextExercise, bonusTextExercise]; -const textExerciseResult = { exerciseId: textExercise.id, achievedScore: 60, achievedPoints: 6, maxScore: textExercise.maxPoints } as ExerciseResult; +const textExerciseResult = { + exerciseId: textExercise.id, + achievedScore: 60, + achievedPoints: 6, + maxScore: textExercise.maxPoints, +} as ExerciseResult; describe('ExamResultOverviewComponent', () => { beforeEach(() => { return TestBed.configureTestingModule({ imports: [RouterTestingModule.withRoutes([]), MockModule(NgbModule), HttpClientTestingModule], - declarations: [ExamResultOverviewComponent, MockComponent(FaIconComponent), MockPipe(ArtemisTranslatePipe), MockComponent(GradingKeyTableComponent)], + declarations: [ + ExamResultOverviewComponent, + MockComponent(FaIconComponent), + MockPipe(ArtemisTranslatePipe), + MockComponent(GradingKeyTableComponent), + MockComponent(CollapsibleCardComponent), + ], providers: [MockProvider(ExerciseService)], }) .compileComponents() @@ -284,4 +296,46 @@ describe('ExamResultOverviewComponent', () => { expect(consoleErrorSpy).not.toHaveBeenCalled(); }); }); + + describe('summedAchievedExerciseScorePercentage', () => { + it('should be called when overallScoreAchieved is not defined in DTO from server', () => { + //@ts-ignore spying on private method + const summedAchievedExerciseScorePercentageSpy = jest.spyOn(component, 'summedAchievedExerciseScorePercentage'); + component.studentExamWithGrade.studentResult.overallScoreAchieved = undefined; + component.exerciseInfos = {}; + + component.ngOnInit(); + + expect(summedAchievedExerciseScorePercentageSpy).toHaveBeenCalledOnce(); + }); + + it('should be called when overallScoreAchieved is 0 (default value, might be set as initial value because not defined from server DTO)', () => { + //@ts-ignore spying on private method + const summedAchievedExerciseScorePercentageSpy = jest.spyOn(component, 'summedAchievedExerciseScorePercentage'); + component.studentExamWithGrade.studentResult.overallScoreAchieved = 0; + component.exerciseInfos = {}; + + component.ngOnInit(); + + expect(summedAchievedExerciseScorePercentageSpy).toHaveBeenCalledOnce(); + }); + + it('should calculate achieved percentage from exercise info properly', () => { + //@ts-ignore spying on private method + const summedAchievedExerciseScorePercentageSpy = jest.spyOn(component, 'summedAchievedExerciseScorePercentage'); + const exerciseInfosWithAchievedPercentage = { + 1: { achievedPercentage: 80 }, + 2: { achievedPercentage: 60 }, + 3: { achievedPercentage: 90 }, + }; + //@ts-ignore missing attributes + component.exerciseInfos = exerciseInfosWithAchievedPercentage; + component.studentExamWithGrade.studentResult.overallScoreAchieved = undefined; + + component.ngOnInit(); + + expect(summedAchievedExerciseScorePercentageSpy).toHaveBeenCalledOnce(); + expect(component.overallAchievedPercentageRoundedByCourseSettings).toBe(76.67); + }); + }); }); From 798620a4f9769ebe8d0a35e2577f2f09f0588080 Mon Sep 17 00:00:00 2001 From: Jakub Riegel Date: Sun, 7 Jan 2024 16:32:31 +0100 Subject: [PATCH 17/25] Plagiarism checks: Hide other plagiarism case submission before exercise due date (#7582) --- .../service/RepositoryAccessService.java | 7 ++- .../service/plagiarism/PlagiarismService.java | 61 +++++++++++++------ .../web/rest/ModelingSubmissionResource.java | 2 +- .../web/rest/TextSubmissionResource.java | 3 +- .../plagiarism-case-review.component.html | 1 + ...sm-case-student-detail-view.component.html | 2 +- .../modeling-submission-viewer.component.ts | 24 ++++---- .../plagiarism-split-view.component.html | 17 ++++-- .../plagiarism-split-view.component.ts | 4 ++ .../text-submission-viewer.component.ts | 13 ++-- .../ModelingSubmissionIntegrationTest.java | 17 ++++++ .../RepositoryIntegrationTest.java | 20 +++++- .../text/TextSubmissionIntegrationTest.java | 17 ++++++ ...deling-submission-viewer.component.spec.ts | 16 +++++ .../text-submission-viewer.component.spec.ts | 11 ++++ 15 files changed, 167 insertions(+), 48 deletions(-) diff --git a/src/main/java/de/tum/in/www1/artemis/service/RepositoryAccessService.java b/src/main/java/de/tum/in/www1/artemis/service/RepositoryAccessService.java index 4b0bc207736c..79bfde0362ff 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/RepositoryAccessService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/RepositoryAccessService.java @@ -57,8 +57,9 @@ public void checkAccessRepositoryElseThrow(ProgrammingExerciseParticipation prog // Error case 1: The user does not have permissions to push into the repository and the user is not notified for a related plagiarism case. boolean hasPermissions = participationAuthCheckService.canAccessParticipation(programmingParticipation, user); - boolean userWasNotifiedAboutPlagiarismCase = plagiarismService.wasUserNotifiedByInstructor(programmingParticipation.getId(), user.getLogin()); - if (!hasPermissions && !userWasNotifiedAboutPlagiarismCase) { + var exerciseDueDate = programmingExercise.isExamExercise() ? programmingExercise.getExerciseGroup().getExam().getEndDate() : programmingExercise.getDueDate(); + boolean hasAccessToSubmission = plagiarismService.hasAccessToSubmission(programmingParticipation.getId(), user.getLogin(), exerciseDueDate); + if (!hasPermissions && !hasAccessToSubmission) { throw new AccessUnauthorizedException(); } @@ -106,7 +107,7 @@ else if (programmingExercise.getSubmissionPolicy() instanceof LockRepositoryPoli // to read the student's repository. // But the student should still be able to access if they are notified for a related plagiarism case. if ((isStudent || (isTeachingAssistant && repositoryActionType != RepositoryActionType.READ)) - && !examSubmissionService.isAllowedToSubmitDuringExam(programmingExercise, user, false) && !userWasNotifiedAboutPlagiarismCase) { + && !examSubmissionService.isAllowedToSubmitDuringExam(programmingExercise, user, false) && !hasAccessToSubmission) { throw new AccessForbiddenException(); } } diff --git a/src/main/java/de/tum/in/www1/artemis/service/plagiarism/PlagiarismService.java b/src/main/java/de/tum/in/www1/artemis/service/plagiarism/PlagiarismService.java index f4a5ea141cae..4d5cc113231c 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/plagiarism/PlagiarismService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/plagiarism/PlagiarismService.java @@ -1,7 +1,13 @@ package de.tum.in.www1.artemis.service.plagiarism; +import static java.util.function.Predicate.isEqual; +import static java.util.function.Predicate.not; + +import java.time.ZonedDateTime; import java.util.HashSet; +import java.util.Objects; import java.util.Set; +import java.util.stream.Stream; import org.springframework.stereotype.Service; @@ -38,11 +44,12 @@ public PlagiarismService(PlagiarismComparisonRepository plagiarismComparisonRepo * Anonymize the submission for the student view. * A student should not see sensitive information but be able to retrieve both answers from both students for the comparison * - * @param submission the submission to anonymize. - * @param userLogin the user login of the student asking to see his plagiarism comparison. + * @param submission the submission to anonymize. + * @param userLogin the user login of the student asking to see his plagiarism comparison. + * @param exerciseDueDate due date of the exercise. */ - public void checkAccessAndAnonymizeSubmissionForStudent(Submission submission, String userLogin) { - if (!wasUserNotifiedByInstructor(submission.getId(), userLogin)) { + public void checkAccessAndAnonymizeSubmissionForStudent(Submission submission, String userLogin, ZonedDateTime exerciseDueDate) { + if (!hasAccessToSubmission(submission.getId(), userLogin, exerciseDueDate)) { throw new AccessForbiddenException("This plagiarism submission is not related to the requesting user or the user has not been notified yet."); } submission.setParticipation(null); @@ -50,29 +57,43 @@ public void checkAccessAndAnonymizeSubmissionForStudent(Submission submission, S submission.setSubmissionDate(null); } + /** + * A student should not see both answers from both students for the comparison before the due date + * + * @param submissionId the id of the submission to check. + * @param userLogin the user login of the student asking to see his plagiarism comparison. + * @param exerciseDueDate due date of the exercise. + * @return true is the user has access to the submission + */ + public boolean hasAccessToSubmission(Long submissionId, String userLogin, ZonedDateTime exerciseDueDate) { + var comparisonOptional = plagiarismComparisonRepository.findBySubmissionA_SubmissionIdOrSubmissionB_SubmissionId(submissionId, submissionId); + return comparisonOptional.filter(not(Set::isEmpty)).isPresent() + && isOwnSubmissionOrIsAfterExerciseDueDate(submissionId, userLogin, comparisonOptional.get(), exerciseDueDate) + && wasUserNotifiedByInstructor(userLogin, comparisonOptional.get()); + } + + private boolean isOwnSubmissionOrIsAfterExerciseDueDate(Long submissionId, String userLogin, Set> comparisons, ZonedDateTime exerciseDueDate) { + var isOwnSubmission = comparisons.stream().flatMap(it -> Stream.of(it.getSubmissionA(), it.getSubmissionB())).filter(Objects::nonNull) + .filter(it -> it.getSubmissionId() == submissionId).findFirst().map(PlagiarismSubmission::getStudentLogin).filter(isEqual(userLogin)).isPresent(); + return isOwnSubmission || exerciseDueDate.isBefore(ZonedDateTime.now()); + } + /** * Checks whether the student with the given user login is involved in a plagiarism case which contains the given submissionId and the student is notified by the instructor. * - * @param submissionId the id of a submissions that will be checked in plagiarism cases - * @param userLogin the user login of the student + * @param userLogin the user login of the student * @return true if the student with user login owns one of the submissions in a PlagiarismComparison which contains the given submissionId and is notified by the instructor, * otherwise false */ - public boolean wasUserNotifiedByInstructor(Long submissionId, String userLogin) { - var comparisonOptional = plagiarismComparisonRepository.findBySubmissionA_SubmissionIdOrSubmissionB_SubmissionId(submissionId, submissionId); - + private boolean wasUserNotifiedByInstructor(String userLogin, Set> comparisons) { // disallow requests from users who are not notified about this case: - if (comparisonOptional.isPresent()) { - var comparisons = comparisonOptional.get(); - return comparisons.stream() - .anyMatch(comparison -> (comparison.getSubmissionA().getPlagiarismCase() != null - && (comparison.getSubmissionA().getPlagiarismCase().getPost() != null || comparison.getSubmissionA().getPlagiarismCase().getVerdict() != null) - && (comparison.getSubmissionA().getStudentLogin().equals(userLogin))) - || (comparison.getSubmissionB().getPlagiarismCase() != null - && (comparison.getSubmissionB().getPlagiarismCase().getPost() != null || comparison.getSubmissionB().getPlagiarismCase().getVerdict() != null) - && (comparison.getSubmissionB().getStudentLogin().equals(userLogin)))); - } - return false; + return comparisons.stream() + .anyMatch(comparison -> (comparison.getSubmissionA().getPlagiarismCase() != null + && (comparison.getSubmissionA().getPlagiarismCase().getPost() != null || comparison.getSubmissionA().getPlagiarismCase().getVerdict() != null) + && (comparison.getSubmissionA().getStudentLogin().equals(userLogin))) + || (comparison.getSubmissionB().getPlagiarismCase() != null + && (comparison.getSubmissionB().getPlagiarismCase().getPost() != null || comparison.getSubmissionB().getPlagiarismCase().getVerdict() != null) + && (comparison.getSubmissionB().getStudentLogin().equals(userLogin)))); } /** diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/ModelingSubmissionResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/ModelingSubmissionResource.java index 9620d8799343..3ee392a4b975 100644 --- a/src/main/java/de/tum/in/www1/artemis/web/rest/ModelingSubmissionResource.java +++ b/src/main/java/de/tum/in/www1/artemis/web/rest/ModelingSubmissionResource.java @@ -187,7 +187,7 @@ public ResponseEntity getModelingSubmission(@PathVariable Lo if (!authCheckService.isAllowedToAssessExercise(modelingExercise, user, resultId)) { // anonymize and throw exception if not authorized to view submission - plagiarismService.checkAccessAndAnonymizeSubmissionForStudent(modelingSubmission, userRepository.getUser().getLogin()); + plagiarismService.checkAccessAndAnonymizeSubmissionForStudent(modelingSubmission, userRepository.getUser().getLogin(), modelingExercise.getDueDate()); return ResponseEntity.ok(modelingSubmission); } diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/TextSubmissionResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/TextSubmissionResource.java index e5657e4b519f..372f310a84ba 100644 --- a/src/main/java/de/tum/in/www1/artemis/web/rest/TextSubmissionResource.java +++ b/src/main/java/de/tum/in/www1/artemis/web/rest/TextSubmissionResource.java @@ -147,7 +147,8 @@ public ResponseEntity getTextSubmissionWithResults(@PathVariable if (!authCheckService.isAtLeastTeachingAssistantForExercise(textSubmission.getParticipation().getExercise())) { // anonymize and throw exception if not authorized to view submission - plagiarismService.checkAccessAndAnonymizeSubmissionForStudent(textSubmission, userRepository.getUser().getLogin()); + plagiarismService.checkAccessAndAnonymizeSubmissionForStudent(textSubmission, userRepository.getUser().getLogin(), + textSubmission.getParticipation().getExercise().getDueDate()); return ResponseEntity.ok(textSubmission); } diff --git a/src/main/webapp/app/course/plagiarism-cases/shared/review/plagiarism-case-review.component.html b/src/main/webapp/app/course/plagiarism-cases/shared/review/plagiarism-case-review.component.html index 6ef732f504c4..dad09c520113 100644 --- a/src/main/webapp/app/course/plagiarism-cases/shared/review/plagiarism-case-review.component.html +++ b/src/main/webapp/app/course/plagiarism-cases/shared/review/plagiarism-case-review.component.html @@ -12,6 +12,7 @@ [exercise]="plagiarismCase.exercise!" [sortByStudentLogin]="forStudent ? 'Your submission' : plagiarismCase.student!.login!" [splitControlSubject]="splitControlSubject" + [forStudent]="forStudent" > diff --git a/src/main/webapp/app/course/plagiarism-cases/student-view/detail-view/plagiarism-case-student-detail-view.component.html b/src/main/webapp/app/course/plagiarism-cases/student-view/detail-view/plagiarism-case-student-detail-view.component.html index 095ec0130e45..62379b945c23 100644 --- a/src/main/webapp/app/course/plagiarism-cases/student-view/detail-view/plagiarism-case-student-detail-view.component.html +++ b/src/main/webapp/app/course/plagiarism-cases/student-view/detail-view/plagiarism-case-student-detail-view.component.html @@ -84,7 +84,7 @@

{{ 'artemisApp.plagiarism.plagiarismCases.conversation' | artemisTranslate }

{{ 'artemisApp.plagiarism.plagiarismCases.comparisons' | artemisTranslate }}

- +
diff --git a/src/main/webapp/app/exercises/shared/plagiarism/plagiarism-split-view/modeling-submission-viewer/modeling-submission-viewer.component.ts b/src/main/webapp/app/exercises/shared/plagiarism/plagiarism-split-view/modeling-submission-viewer/modeling-submission-viewer.component.ts index f41a66dc967d..73824171b387 100644 --- a/src/main/webapp/app/exercises/shared/plagiarism/plagiarism-split-view/modeling-submission-viewer/modeling-submission-viewer.component.ts +++ b/src/main/webapp/app/exercises/shared/plagiarism/plagiarism-split-view/modeling-submission-viewer/modeling-submission-viewer.component.ts @@ -14,6 +14,7 @@ import { UMLModel } from '@ls1intum/apollon'; export class ModelingSubmissionViewerComponent implements OnChanges { @Input() exercise: ModelingExercise; @Input() plagiarismSubmission: PlagiarismSubmission; + @Input() hideContent: boolean; public loading: boolean; public submissionModel: UMLModel; @@ -22,19 +23,20 @@ export class ModelingSubmissionViewerComponent implements OnChanges { ngOnChanges(changes: SimpleChanges): void { if (changes.plagiarismSubmission) { - this.loading = true; - const currentPlagiarismSubmission: PlagiarismSubmission = changes.plagiarismSubmission.currentValue; - this.modelingSubmissionService.getSubmissionWithoutLock(currentPlagiarismSubmission.submissionId).subscribe({ - next: (submission: ModelingSubmission) => { - this.loading = false; - this.submissionModel = JSON.parse(submission.model!) as UMLModel; - }, - error: () => { - this.loading = false; - }, - }); + if (!this.hideContent) { + this.loading = true; + this.modelingSubmissionService.getSubmissionWithoutLock(currentPlagiarismSubmission.submissionId).subscribe({ + next: (submission: ModelingSubmission) => { + this.loading = false; + this.submissionModel = JSON.parse(submission.model!) as UMLModel; + }, + error: () => { + this.loading = false; + }, + }); + } } } } diff --git a/src/main/webapp/app/exercises/shared/plagiarism/plagiarism-split-view/plagiarism-split-view.component.html b/src/main/webapp/app/exercises/shared/plagiarism/plagiarism-split-view/plagiarism-split-view.component.html index e1bae0cb1d2b..d017955e004e 100644 --- a/src/main/webapp/app/exercises/shared/plagiarism/plagiarism-split-view/plagiarism-split-view.component.html +++ b/src/main/webapp/app/exercises/shared/plagiarism/plagiarism-split-view/plagiarism-split-view.component.html @@ -2,20 +2,29 @@
@if (plagiarismComparison) { @if (isModelingExercise) { - + } @if (isProgrammingOrTextExercise) { - + } }
@if (plagiarismComparison) { @if (isModelingExercise) { - + } @if (isProgrammingOrTextExercise) { - + } }
diff --git a/src/main/webapp/app/exercises/shared/plagiarism/plagiarism-split-view/plagiarism-split-view.component.ts b/src/main/webapp/app/exercises/shared/plagiarism/plagiarism-split-view/plagiarism-split-view.component.ts index 4d8d92210159..f4655180907b 100644 --- a/src/main/webapp/app/exercises/shared/plagiarism/plagiarism-split-view/plagiarism-split-view.component.ts +++ b/src/main/webapp/app/exercises/shared/plagiarism/plagiarism-split-view/plagiarism-split-view.component.ts @@ -9,6 +9,7 @@ import { PlagiarismSubmission } from 'app/exercises/shared/plagiarism/types/Plag import { PlagiarismCasesService } from 'app/course/plagiarism-cases/shared/plagiarism-cases.service'; import { HttpResponse } from '@angular/common/http'; import { SimpleMatch } from 'app/exercises/shared/plagiarism/types/PlagiarismMatch'; +import dayjs from 'dayjs/esm'; @Directive({ selector: '[jhiPane]' }) export class SplitPaneDirective { @@ -25,6 +26,7 @@ export class PlagiarismSplitViewComponent implements AfterViewInit, OnChanges, O @Input() exercise: Exercise; @Input() splitControlSubject: Subject; @Input() sortByStudentLogin: string; + @Input() forStudent: boolean; @ViewChildren(SplitPaneDirective) panes!: QueryList; @@ -38,6 +40,8 @@ export class PlagiarismSplitViewComponent implements AfterViewInit, OnChanges, O public matchesA: Map; public matchesB: Map; + readonly dayjs = dayjs; + constructor(private plagiarismCasesService: PlagiarismCasesService) {} /** diff --git a/src/main/webapp/app/exercises/shared/plagiarism/plagiarism-split-view/text-submission-viewer/text-submission-viewer.component.ts b/src/main/webapp/app/exercises/shared/plagiarism/plagiarism-split-view/text-submission-viewer/text-submission-viewer.component.ts index 075715c77381..fe9a0dfc72e0 100644 --- a/src/main/webapp/app/exercises/shared/plagiarism/plagiarism-split-view/text-submission-viewer/text-submission-viewer.component.ts +++ b/src/main/webapp/app/exercises/shared/plagiarism/plagiarism-split-view/text-submission-viewer/text-submission-viewer.component.ts @@ -23,6 +23,7 @@ export class TextSubmissionViewerComponent implements OnChanges { @Input() exercise: ProgrammingExercise | TextExercise; @Input() matches: Map; @Input() plagiarismSubmission: PlagiarismSubmission; + @Input() hideContent: boolean; /** * Name of the currently selected file. @@ -73,12 +74,14 @@ export class TextSubmissionViewerComponent implements OnChanges { ngOnChanges(changes: SimpleChanges): void { if (changes.plagiarismSubmission) { const currentPlagiarismSubmission: PlagiarismSubmission = changes.plagiarismSubmission.currentValue; - this.loading = true; + if (!this.hideContent) { + this.loading = true; - if (this.exercise.type === ExerciseType.PROGRAMMING) { - this.loadProgrammingExercise(currentPlagiarismSubmission); - } else { - this.loadTextExercise(currentPlagiarismSubmission); + if (this.exercise.type === ExerciseType.PROGRAMMING) { + this.loadProgrammingExercise(currentPlagiarismSubmission); + } else { + this.loadTextExercise(currentPlagiarismSubmission); + } } } } diff --git a/src/test/java/de/tum/in/www1/artemis/exercise/modelingexercise/ModelingSubmissionIntegrationTest.java b/src/test/java/de/tum/in/www1/artemis/exercise/modelingexercise/ModelingSubmissionIntegrationTest.java index 5b6ec29e766d..0ed20fbbe159 100644 --- a/src/test/java/de/tum/in/www1/artemis/exercise/modelingexercise/ModelingSubmissionIntegrationTest.java +++ b/src/test/java/de/tum/in/www1/artemis/exercise/modelingexercise/ModelingSubmissionIntegrationTest.java @@ -487,6 +487,23 @@ void getModelSubmissionWithResult_notInvolved_notAllowed() throws Exception { request.get("/api/modeling-submissions/" + submission.getId(), HttpStatus.FORBIDDEN, ModelingSubmission.class); } + @Test + @WithMockUser(value = TEST_PREFIX + "student1", roles = "USER") + void getModelSubmissionWithResult_notOwner_beforeDueDate_notAllowed() throws Exception { + var submission = ParticipationFactory.generateModelingSubmission(validModel, true); + submission = modelingExerciseUtilService.addModelingSubmission(classExercise, submission, TEST_PREFIX + "student2"); + + var plagiarismComparison = new PlagiarismComparison(); + var submissionA = new PlagiarismSubmission(); + submissionA.setStudentLogin(TEST_PREFIX + "student2"); + submissionA.setSubmissionId(submission.getId()); + plagiarismComparison.setSubmissionA(submissionA); + + plagiarismComparisonRepository.save(plagiarismComparison); + + request.get("/api/modeling-submissions/" + submission.getId(), HttpStatus.FORBIDDEN, ModelingSubmission.class); + } + @Test @WithMockUser(username = TEST_PREFIX + "tutor1", roles = "TA") void getModelSubmission_lockLimitReached_success() throws Exception { diff --git a/src/test/java/de/tum/in/www1/artemis/exercise/programmingexercise/RepositoryIntegrationTest.java b/src/test/java/de/tum/in/www1/artemis/exercise/programmingexercise/RepositoryIntegrationTest.java index c0a9c4aea653..a71dae4b4518 100644 --- a/src/test/java/de/tum/in/www1/artemis/exercise/programmingexercise/RepositoryIntegrationTest.java +++ b/src/test/java/de/tum/in/www1/artemis/exercise/programmingexercise/RepositoryIntegrationTest.java @@ -510,6 +510,7 @@ void testGetFilesAsDifferentStudentWithRelevantPlagiarismCase() throws Exception @Test @WithMockUser(username = TEST_PREFIX + "student2", roles = "USER") void testGetFileAsDifferentStudentWithRelevantPlagiarismCase() throws Exception { + programmingExercise.setDueDate(ZonedDateTime.now().minusHours(1)); programmingExerciseRepository.save(programmingExercise); addPlagiarismCaseToProgrammingExercise(TEST_PREFIX + "student1", TEST_PREFIX + "student2"); @@ -521,6 +522,19 @@ void testGetFileAsDifferentStudentWithRelevantPlagiarismCase() throws Exception assertThat(new String(file)).isEqualTo(currentLocalFileContent); } + @Test + @WithMockUser(username = TEST_PREFIX + "student2", roles = "USER") + void testCannotGetFileAsDifferentStudentWithRelevantPlagiarismCaseBeforeExerciseDueDate() throws Exception { + programmingExercise.setDueDate(ZonedDateTime.now().plusHours(1)); + programmingExerciseRepository.save(programmingExercise); + + addPlagiarismCaseToProgrammingExercise(TEST_PREFIX + "student1", TEST_PREFIX + "student2"); + + LinkedMultiValueMap params = new LinkedMultiValueMap<>(); + params.add("file", currentLocalFileName); + request.get(studentRepoBaseUrl + participation.getId() + "/file", HttpStatus.FORBIDDEN, byte[].class, params); + } + @Test @WithMockUser(username = TEST_PREFIX + "student1", roles = "USER") void testGetFileWithRelevantPlagiarismCaseAfterExam() throws Exception { @@ -529,7 +543,8 @@ void testGetFileWithRelevantPlagiarismCaseAfterExam() throws Exception { // The calculated exam end date (startDate of exam + workingTime of studentExam (7200 seconds)) // should be in the past for this test. - exam.setStartDate(ZonedDateTime.now().minusHours(3)); + exam.setStartDate(ZonedDateTime.now().minusHours(4)); + exam.setEndDate(ZonedDateTime.now().minusHours(1)); examRepository.save(exam); addPlagiarismCaseToProgrammingExercise(TEST_PREFIX + "student2", TEST_PREFIX + "student1"); @@ -566,7 +581,8 @@ void testGetFilesWithRelevantPlagiarismCaseAfterExam() throws Exception { // The calculated exam end date (startDate of exam + workingTime of studentExam (7200 seconds)) // should be in the past for this test. - exam.setStartDate(ZonedDateTime.now().minusHours(3)); + exam.setStartDate(ZonedDateTime.now().minusHours(4)); + exam.setEndDate(ZonedDateTime.now().minusHours(1)); examRepository.save(exam); // student1 is notified. diff --git a/src/test/java/de/tum/in/www1/artemis/text/TextSubmissionIntegrationTest.java b/src/test/java/de/tum/in/www1/artemis/text/TextSubmissionIntegrationTest.java index 214c1028a87c..b87ed55191b9 100644 --- a/src/test/java/de/tum/in/www1/artemis/text/TextSubmissionIntegrationTest.java +++ b/src/test/java/de/tum/in/www1/artemis/text/TextSubmissionIntegrationTest.java @@ -27,6 +27,7 @@ import de.tum.in.www1.artemis.domain.plagiarism.PlagiarismCase; import de.tum.in.www1.artemis.domain.plagiarism.PlagiarismComparison; import de.tum.in.www1.artemis.domain.plagiarism.PlagiarismSubmission; +import de.tum.in.www1.artemis.domain.plagiarism.modeling.ModelingSubmissionElement; import de.tum.in.www1.artemis.domain.plagiarism.text.TextSubmissionElement; import de.tum.in.www1.artemis.exercise.ExerciseUtilService; import de.tum.in.www1.artemis.exercise.textexercise.TextExerciseFactory; @@ -178,6 +179,22 @@ void getTextSubmissionWithResult_notInvolved_notAllowed() throws Exception { request.get("/api/text-submissions/" + this.textSubmission.getId(), HttpStatus.FORBIDDEN, TextSubmission.class); } + @Test + @WithMockUser(value = TEST_PREFIX + "student1", roles = "USER") + void getTextSubmissionWithResult_notOwner_beforeDueDate_notAllowed() throws Exception { + textSubmission = textExerciseUtilService.saveTextSubmission(releasedTextExercise, textSubmission, TEST_PREFIX + "student1"); + + var plagiarismComparison = new PlagiarismComparison(); + var submissionA = new PlagiarismSubmission(); + submissionA.setStudentLogin(TEST_PREFIX + "student2"); + submissionA.setSubmissionId(textSubmission.getId()); + plagiarismComparison.setSubmissionA(submissionA); + + plagiarismComparisonRepository.save(plagiarismComparison); + + request.get("/api/text-submissions/" + textSubmission.getId(), HttpStatus.FORBIDDEN, TextSubmission.class); + } + @Test @WithMockUser(username = TEST_PREFIX + "tutor1", roles = "TA") void getAllTextSubmissions_studentHiddenForTutor() throws Exception { diff --git a/src/test/javascript/spec/component/plagiarism/modeling-submission-viewer.component.spec.ts b/src/test/javascript/spec/component/plagiarism/modeling-submission-viewer.component.spec.ts index 33fc8e603e9a..815ec61969c2 100644 --- a/src/test/javascript/spec/component/plagiarism/modeling-submission-viewer.component.spec.ts +++ b/src/test/javascript/spec/component/plagiarism/modeling-submission-viewer.component.spec.ts @@ -58,4 +58,20 @@ describe('Modeling Submission Viewer Component', () => { expect(modelingSubmissionService.getSubmissionWithoutLock).toHaveBeenCalledWith(1); }); + + it('should not fetch the submission if hideContent is true', () => { + jest.spyOn(modelingSubmissionService, 'getSubmissionWithoutLock').mockReturnValue(of(modelingSubmission)); + comp.hideContent = true; + + comp.ngOnChanges({ + plagiarismSubmission: { + previousValue: null, + isFirstChange: () => true, + firstChange: true, + currentValue: { submissionId: 1 }, + }, + }); + + expect(modelingSubmissionService.getSubmissionWithoutLock).not.toHaveBeenCalled(); + }); }); diff --git a/src/test/javascript/spec/component/plagiarism/text-submission-viewer.component.spec.ts b/src/test/javascript/spec/component/plagiarism/text-submission-viewer.component.spec.ts index 699d50ced723..d39c771a6390 100644 --- a/src/test/javascript/spec/component/plagiarism/text-submission-viewer.component.spec.ts +++ b/src/test/javascript/spec/component/plagiarism/text-submission-viewer.component.spec.ts @@ -75,6 +75,17 @@ describe('Text Submission Viewer Component', () => { expect(comp.isProgrammingExercise).toBeTrue(); }); + it('does not fetch a programming submission', () => { + jest.spyOn(repositoryService, 'getRepositoryContent').mockReturnValue(of({})); + comp.hideContent = true; + + comp.ngOnChanges({ + plagiarismSubmission: { currentValue: { submissionId: 2 } } as SimpleChange, + }); + + expect(repositoryService.getRepositoryContent).not.toHaveBeenCalled(); + }); + it('sorts and filters the files when fetching a programming submission', () => { comp.exercise = { type: ExerciseType.PROGRAMMING } as ProgrammingExercise; From 00d91725df2eea6641105fad256a147b7657e2dd Mon Sep 17 00:00:00 2001 From: Mateus Messias Mendes <120647937+mateusmm01@users.noreply.github.com> Date: Sun, 7 Jan 2024 16:54:33 +0100 Subject: [PATCH 18/25] Development: Add security check and profile restrictions for build queue representation in local continuous integration (#7843) --- .../websocket/WebsocketConfiguration.java | 37 ++++- .../artemis/repository/UserRepository.java | 25 ++++ .../localci/LocalCIQueueWebsocketService.java | 133 ++++++++++++++++++ .../LocalCISharedBuildJobQueueService.java | 72 +++------- .../LocalCIBuildQueueWebsocketService.java | 36 +++++ 5 files changed, 241 insertions(+), 62 deletions(-) create mode 100644 src/main/java/de/tum/in/www1/artemis/service/connectors/localci/LocalCIQueueWebsocketService.java diff --git a/src/main/java/de/tum/in/www1/artemis/config/websocket/WebsocketConfiguration.java b/src/main/java/de/tum/in/www1/artemis/config/websocket/WebsocketConfiguration.java index 91c45691f4dc..ef253d5cdc73 100644 --- a/src/main/java/de/tum/in/www1/artemis/config/websocket/WebsocketConfiguration.java +++ b/src/main/java/de/tum/in/www1/artemis/config/websocket/WebsocketConfiguration.java @@ -2,6 +2,8 @@ import static de.tum.in.www1.artemis.web.websocket.ResultWebsocketService.getExerciseIdFromNonPersonalExerciseResultDestination; import static de.tum.in.www1.artemis.web.websocket.ResultWebsocketService.isNonPersonalExerciseResultDestination; +import static de.tum.in.www1.artemis.web.websocket.localci.LocalCIBuildQueueWebsocketService.isBuildQueueAdminDestination; +import static de.tum.in.www1.artemis.web.websocket.localci.LocalCIBuildQueueWebsocketService.isBuildQueueCourseDestination; import static de.tum.in.www1.artemis.web.websocket.team.ParticipationTeamWebsocketService.*; import java.net.InetSocketAddress; @@ -46,13 +48,11 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.Iterators; +import de.tum.in.www1.artemis.domain.Course; import de.tum.in.www1.artemis.domain.Exercise; import de.tum.in.www1.artemis.domain.User; import de.tum.in.www1.artemis.domain.participation.StudentParticipation; -import de.tum.in.www1.artemis.repository.ExamRepository; -import de.tum.in.www1.artemis.repository.ExerciseRepository; -import de.tum.in.www1.artemis.repository.StudentParticipationRepository; -import de.tum.in.www1.artemis.repository.UserRepository; +import de.tum.in.www1.artemis.repository.*; import de.tum.in.www1.artemis.security.Role; import de.tum.in.www1.artemis.security.jwt.JWTFilter; import de.tum.in.www1.artemis.security.jwt.TokenProvider; @@ -96,9 +96,11 @@ public class WebsocketConfiguration extends DelegatingWebSocketMessageBrokerConf @Value("${spring.websocket.broker.password}") private String brokerPassword; + private final CourseRepository courseRepository; + public WebsocketConfiguration(MappingJackson2HttpMessageConverter springMvcJacksonConverter, TaskScheduler messageBrokerTaskScheduler, TokenProvider tokenProvider, StudentParticipationRepository studentParticipationRepository, AuthorizationCheckService authorizationCheckService, ExerciseRepository exerciseRepository, - UserRepository userRepository, ExamRepository examRepository) { + UserRepository userRepository, ExamRepository examRepository, CourseRepository courseRepository) { this.objectMapper = springMvcJacksonConverter.getObjectMapper(); this.messageBrokerTaskScheduler = messageBrokerTaskScheduler; this.tokenProvider = tokenProvider; @@ -107,6 +109,7 @@ public WebsocketConfiguration(MappingJackson2HttpMessageConverter springMvcJacks this.exerciseRepository = exerciseRepository; this.userRepository = userRepository; this.examRepository = examRepository; + this.courseRepository = courseRepository; } @Override @@ -262,12 +265,34 @@ public Message preSend(@NotNull Message message, @NotNull MessageChannel c /** * Returns whether the subscription of the given principal to the given destination is permitted + * Database calls should be avoided as much as possible in this method. + * Only for very specific topics, database calls are allowed. * * @param principal User principal of the user who wants to subscribe * @param destination Destination topic to which the user wants to subscribe * @return flag whether subscription is allowed */ private boolean allowSubscription(Principal principal, String destination) { + /* + * IMPORTANT: Avoid database calls in this method as much as possible (e.g. checking if the user + * is an instructor in a course) + * This method is called for every subscription request, so it should be as fast as possible. + * If you need to do a database call, make sure to first check if the destination is valid for your specific + * use case. + */ + + if (isBuildQueueAdminDestination(destination)) { + var user = userRepository.getUserWithAuthorities(principal.getName()); + return authorizationCheckService.isAdmin(user); + } + + Optional courseId = isBuildQueueCourseDestination(destination); + if (courseId.isPresent()) { + Course course = courseRepository.findByIdElseThrow(courseId.get()); + var user = userRepository.getUserWithGroupsAndAuthorities(principal.getName()); + return authorizationCheckService.isAtLeastInstructorInCourse(course, user); + } + if (isParticipationTeamDestination(destination)) { Long participationId = getParticipationIdFromDestination(destination); return isParticipationOwnedByUser(principal, participationId); @@ -288,7 +313,7 @@ private boolean allowSubscription(Principal principal, String destination) { var examId = getExamIdFromExamRootDestination(destination); if (examId.isPresent()) { var exam = examRepository.findByIdElseThrow(examId.get()); - User user = userRepository.getUserWithGroupsAndAuthorities(principal.getName()); + var user = userRepository.getUserWithGroupsAndAuthorities(principal.getName()); return authorizationCheckService.isAtLeastInstructorInCourse(exam.getCourse(), user); } return true; diff --git a/src/main/java/de/tum/in/www1/artemis/repository/UserRepository.java b/src/main/java/de/tum/in/www1/artemis/repository/UserRepository.java index dbf6fc927c34..a24406cc6861 100644 --- a/src/main/java/de/tum/in/www1/artemis/repository/UserRepository.java +++ b/src/main/java/de/tum/in/www1/artemis/repository/UserRepository.java @@ -74,6 +74,9 @@ public interface UserRepository extends JpaRepository, JpaSpecificat @EntityGraph(type = LOAD, attributePaths = { "groups", "authorities" }) Optional findOneWithGroupsAndAuthoritiesByLogin(String login); + @EntityGraph(type = LOAD, attributePaths = { "authorities" }) + Optional findOneWithAuthoritiesByLogin(String login); + @EntityGraph(type = LOAD, attributePaths = { "groups", "authorities" }) Optional findOneWithGroupsAndAuthoritiesByEmail(String email); @@ -499,6 +502,17 @@ default User getUserWithGroupsAndAuthorities() { return unwrapOptionalUser(user, currentUserLogin); } + /** + * Get user with authorities of currently logged-in user + * + * @return currently logged-in user + */ + default User getUserWithAuthorities() { + String currentUserLogin = getCurrentUserLogin(); + Optional user = findOneWithAuthoritiesByLogin(currentUserLogin); + return unwrapOptionalUser(user, currentUserLogin); + } + /** * Get user with user groups, authorities and organizations of currently logged-in user * @@ -549,6 +563,17 @@ default User getUserWithGroupsAndAuthorities(@NotNull String username) { return unwrapOptionalUser(user, username); } + /** + * Get user with authorities with the username (i.e. user.getLogin() or principal.getName()) + * + * @param username the username of the user who should be retrieved from the database + * @return the user that belongs to the given principal with eagerly loaded authorities + */ + default User getUserWithAuthorities(@NotNull String username) { + Optional user = findOneWithAuthoritiesByLogin(username); + return unwrapOptionalUser(user, username); + } + /** * Finds a single user with groups and authorities using the registration number * diff --git a/src/main/java/de/tum/in/www1/artemis/service/connectors/localci/LocalCIQueueWebsocketService.java b/src/main/java/de/tum/in/www1/artemis/service/connectors/localci/LocalCIQueueWebsocketService.java new file mode 100644 index 000000000000..088e1f2be8a5 --- /dev/null +++ b/src/main/java/de/tum/in/www1/artemis/service/connectors/localci/LocalCIQueueWebsocketService.java @@ -0,0 +1,133 @@ +package de.tum.in.www1.artemis.service.connectors.localci; + +import java.util.Objects; + +import javax.annotation.PostConstruct; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Service; + +import com.hazelcast.collection.IQueue; +import com.hazelcast.collection.ItemEvent; +import com.hazelcast.collection.ItemListener; +import com.hazelcast.core.HazelcastInstance; +import com.hazelcast.map.IMap; +import com.hazelcast.map.listener.EntryAddedListener; +import com.hazelcast.map.listener.EntryRemovedListener; + +import de.tum.in.www1.artemis.service.connectors.localci.dto.LocalCIBuildAgentInformation; +import de.tum.in.www1.artemis.service.connectors.localci.dto.LocalCIBuildJobQueueItem; +import de.tum.in.www1.artemis.web.websocket.localci.LocalCIBuildQueueWebsocketService; + +/** + * This service is responsible for sending build job queue information over websockets. + * It listens to changes in the build job queue and sends the updated information to the client. + * NOTE: This service is only active if the profile "localci" and "scheduling" are active. This avoids sending the + * same information multiple times and thus also avoids unnecessary load on the server. + */ +@Service +@Profile("localci & scheduling") +public class LocalCIQueueWebsocketService { + + private final Logger log = LoggerFactory.getLogger(LocalCIQueueWebsocketService.class); + + private final HazelcastInstance hazelcastInstance; + + private final IQueue queue; + + private final IMap processingJobs; + + private final IMap buildAgentInformation; + + private final LocalCIBuildQueueWebsocketService localCIBuildQueueWebsocketService; + + private final LocalCISharedBuildJobQueueService localCISharedBuildJobQueueService; + + /** + * Instantiates a new Local ci queue websocket service. + * + * @param hazelcastInstance the hazelcast instance + * @param localCIBuildQueueWebsocketService the local ci build queue websocket service + * @param localCISharedBuildJobQueueService the local ci shared build job queue service + */ + public LocalCIQueueWebsocketService(HazelcastInstance hazelcastInstance, LocalCIBuildQueueWebsocketService localCIBuildQueueWebsocketService, + LocalCISharedBuildJobQueueService localCISharedBuildJobQueueService) { + this.hazelcastInstance = hazelcastInstance; + this.localCIBuildQueueWebsocketService = localCIBuildQueueWebsocketService; + this.localCISharedBuildJobQueueService = localCISharedBuildJobQueueService; + this.queue = this.hazelcastInstance.getQueue("buildJobQueue"); + this.processingJobs = this.hazelcastInstance.getMap("processingJobs"); + this.buildAgentInformation = this.hazelcastInstance.getMap("buildAgentInformation"); + } + + /** + * Add listeners for build job queue changes. + */ + @PostConstruct + public void addListeners() { + this.queue.addItemListener(new QueuedBuildJobItemListener(), true); + this.processingJobs.addLocalEntryListener(new ProcessingBuildJobItemListener()); + this.buildAgentInformation.addLocalEntryListener(new BuildAgentListener()); + // localCIBuildQueueWebsocketService will be autowired only if scheduling is active + Objects.requireNonNull(localCIBuildQueueWebsocketService, "localCIBuildQueueWebsocketService must be non-null when scheduling is active."); + } + + private void sendQueuedJobsOverWebsocket(long courseId) { + localCIBuildQueueWebsocketService.sendQueuedBuildJobs(localCISharedBuildJobQueueService.getQueuedJobs()); + localCIBuildQueueWebsocketService.sendQueuedBuildJobsForCourse(courseId, localCISharedBuildJobQueueService.getQueuedJobsForCourse(courseId)); + } + + private void sendProcessingJobsOverWebsocket(long courseId) { + localCIBuildQueueWebsocketService.sendRunningBuildJobs(localCISharedBuildJobQueueService.getProcessingJobs()); + localCIBuildQueueWebsocketService.sendRunningBuildJobsForCourse(courseId, localCISharedBuildJobQueueService.getProcessingJobsForCourse(courseId)); + } + + private void sendBuildAgentInformationOverWebsocket() { + localCIBuildQueueWebsocketService.sendBuildAgentInformation(localCISharedBuildJobQueueService.getBuildAgentInformation()); + } + + private class QueuedBuildJobItemListener implements ItemListener { + + @Override + public void itemAdded(ItemEvent event) { + sendQueuedJobsOverWebsocket(event.getItem().getCourseId()); + } + + @Override + public void itemRemoved(ItemEvent event) { + sendQueuedJobsOverWebsocket(event.getItem().getCourseId()); + } + } + + private class ProcessingBuildJobItemListener implements EntryAddedListener, EntryRemovedListener { + + @Override + public void entryAdded(com.hazelcast.core.EntryEvent event) { + log.debug("CIBuildJobQueueItem added to processing jobs: {}", event.getValue()); + sendProcessingJobsOverWebsocket(event.getValue().getCourseId()); + } + + @Override + public void entryRemoved(com.hazelcast.core.EntryEvent event) { + log.debug("CIBuildJobQueueItem removed from processing jobs: {}", event.getOldValue()); + sendProcessingJobsOverWebsocket(event.getOldValue().getCourseId()); + } + } + + private class BuildAgentListener implements EntryAddedListener, EntryRemovedListener { + + @Override + public void entryAdded(com.hazelcast.core.EntryEvent event) { + log.debug("Build agent added: {}", event.getValue()); + sendBuildAgentInformationOverWebsocket(); + } + + @Override + public void entryRemoved(com.hazelcast.core.EntryEvent event) { + log.debug("Build agent removed: {}", event.getOldValue()); + sendBuildAgentInformationOverWebsocket(); + } + } +} diff --git a/src/main/java/de/tum/in/www1/artemis/service/connectors/localci/LocalCISharedBuildJobQueueService.java b/src/main/java/de/tum/in/www1/artemis/service/connectors/localci/LocalCISharedBuildJobQueueService.java index ab06010c4f7f..b37a1cfb9ac7 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/connectors/localci/LocalCISharedBuildJobQueueService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/connectors/localci/LocalCISharedBuildJobQueueService.java @@ -6,9 +6,10 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.ReentrantLock; +import javax.annotation.PostConstruct; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Profile; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; @@ -19,8 +20,6 @@ import com.hazelcast.core.HazelcastInstance; import com.hazelcast.cp.lock.FencedLock; import com.hazelcast.map.IMap; -import com.hazelcast.map.listener.EntryAddedListener; -import com.hazelcast.map.listener.EntryRemovedListener; import de.tum.in.www1.artemis.domain.Result; import de.tum.in.www1.artemis.domain.participation.Participation; @@ -33,7 +32,6 @@ import de.tum.in.www1.artemis.service.connectors.localci.dto.LocalCIBuildResult; import de.tum.in.www1.artemis.service.programming.ProgrammingExerciseGradingService; import de.tum.in.www1.artemis.service.programming.ProgrammingMessagingService; -import de.tum.in.www1.artemis.web.websocket.localci.LocalCIBuildQueueWebsocketService; import de.tum.in.www1.artemis.web.websocket.programmingSubmission.BuildTriggerWebsocketError; @Service @@ -77,13 +75,10 @@ public class LocalCISharedBuildJobQueueService { */ private final ReentrantLock instanceLock = new ReentrantLock(); - private final LocalCIBuildQueueWebsocketService localCIBuildQueueWebsocketService; - - @Autowired public LocalCISharedBuildJobQueueService(HazelcastInstance hazelcastInstance, ExecutorService localCIBuildExecutorService, LocalCIBuildJobManagementService localCIBuildJobManagementService, ParticipationRepository participationRepository, ProgrammingExerciseGradingService programmingExerciseGradingService, ProgrammingMessagingService programmingMessagingService, - ProgrammingExerciseRepository programmingExerciseRepository, LocalCIBuildQueueWebsocketService localCIBuildQueueWebsocketService) { + ProgrammingExerciseRepository programmingExerciseRepository) { this.hazelcastInstance = hazelcastInstance; this.localCIBuildExecutorService = (ThreadPoolExecutor) localCIBuildExecutorService; this.localCIBuildJobManagementService = localCIBuildJobManagementService; @@ -95,10 +90,14 @@ public LocalCISharedBuildJobQueueService(HazelcastInstance hazelcastInstance, Ex this.processingJobs = this.hazelcastInstance.getMap("processingJobs"); this.sharedLock = this.hazelcastInstance.getCPSubsystem().getLock("buildJobQueueLock"); this.queue = this.hazelcastInstance.getQueue("buildJobQueue"); + } + + /** + * Add listener to the shared build job queue. + */ + @PostConstruct + public void addListener() { this.queue.addItemListener(new QueuedBuildJobItemListener(), true); - this.processingJobs.addLocalEntryListener(new ProcessingBuildJobItemListener()); - this.buildAgentInformation.addLocalEntryListener(new BuildAgentListener()); - this.localCIBuildQueueWebsocketService = localCIBuildQueueWebsocketService; } /** @@ -193,7 +192,7 @@ private void checkAvailabilityAndProcessNextBuild() { updateLocalBuildAgentInformation(); } - log.info("Node has no available threads currently"); + log.debug("Node has no available threads currently"); return; } @@ -356,6 +355,7 @@ private void processBuild(LocalCIBuildJobQueueItem buildJob) { log.warn("Requeueing failed build job: {}", buildJob); buildJob.setRetryCount(buildJob.getRetryCount() + 1); queue.add(buildJob); + checkAvailabilityAndProcessNextBuild(); } else { log.warn("Participation with id {} has been deleted. Cancelling the requeueing of the build job.", participation.getId()); @@ -409,54 +409,14 @@ private ProgrammingExerciseParticipation retrieveParticipationWithRetry(Long par private class QueuedBuildJobItemListener implements ItemListener { @Override - public void itemAdded(ItemEvent item) { - log.debug("CIBuildJobQueueItem added to queue: {}", item.getItem()); + public void itemAdded(ItemEvent event) { + log.debug("CIBuildJobQueueItem added to queue: {}", event.getItem()); checkAvailabilityAndProcessNextBuild(); - localCIBuildQueueWebsocketService.sendQueuedBuildJobs(getQueuedJobs()); - long courseID = item.getItem().getCourseId(); - localCIBuildQueueWebsocketService.sendQueuedBuildJobsForCourse(courseID, getQueuedJobsForCourse(courseID)); - } - - @Override - public void itemRemoved(ItemEvent item) { - log.debug("CIBuildJobQueueItem removed from queue: {}", item.getItem()); - localCIBuildQueueWebsocketService.sendQueuedBuildJobs(getQueuedJobs()); - long courseID = item.getItem().getCourseId(); - localCIBuildQueueWebsocketService.sendQueuedBuildJobsForCourse(courseID, getQueuedJobsForCourse(courseID)); - } - } - - private class ProcessingBuildJobItemListener implements EntryAddedListener, EntryRemovedListener { - - @Override - public void entryAdded(com.hazelcast.core.EntryEvent event) { - log.debug("CIBuildJobQueueItem added to processing jobs: {}", event.getValue()); - localCIBuildQueueWebsocketService.sendRunningBuildJobs(getProcessingJobs()); - long courseID = event.getValue().getCourseId(); - localCIBuildQueueWebsocketService.sendRunningBuildJobsForCourse(courseID, getProcessingJobsForCourse(courseID)); - } - - @Override - public void entryRemoved(com.hazelcast.core.EntryEvent event) { - log.debug("CIBuildJobQueueItem removed from processing jobs: {}", event.getOldValue()); - localCIBuildQueueWebsocketService.sendRunningBuildJobs(getProcessingJobs()); - long courseID = event.getOldValue().getCourseId(); - localCIBuildQueueWebsocketService.sendRunningBuildJobsForCourse(courseID, getProcessingJobsForCourse(courseID)); - } - } - - private class BuildAgentListener implements EntryAddedListener, EntryRemovedListener { - - @Override - public void entryAdded(com.hazelcast.core.EntryEvent event) { - log.debug("Build agent added: {}", event.getValue()); - localCIBuildQueueWebsocketService.sendBuildAgentInformation(getBuildAgentInformation()); } @Override - public void entryRemoved(com.hazelcast.core.EntryEvent event) { - log.debug("Build agent removed: {}", event.getOldValue()); - localCIBuildQueueWebsocketService.sendBuildAgentInformation(getBuildAgentInformation()); + public void itemRemoved(ItemEvent event) { + log.debug("CIBuildJobQueueItem removed from queue: {}", event.getItem()); } } } diff --git a/src/main/java/de/tum/in/www1/artemis/web/websocket/localci/LocalCIBuildQueueWebsocketService.java b/src/main/java/de/tum/in/www1/artemis/web/websocket/localci/LocalCIBuildQueueWebsocketService.java index 2d72397f998a..1694ba02110c 100644 --- a/src/main/java/de/tum/in/www1/artemis/web/websocket/localci/LocalCIBuildQueueWebsocketService.java +++ b/src/main/java/de/tum/in/www1/artemis/web/websocket/localci/LocalCIBuildQueueWebsocketService.java @@ -1,6 +1,9 @@ package de.tum.in.www1.artemis.web.websocket.localci; import java.util.List; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -24,6 +27,8 @@ public class LocalCIBuildQueueWebsocketService { private final WebsocketMessagingService websocketMessagingService; + private static final Pattern COURSE_DESTINATION_PATTERN = Pattern.compile("^/topic/courses/(\\d+)/(queued-jobs|running-jobs)$"); + /** * Constructor for dependency injection * @@ -90,4 +95,35 @@ public void sendBuildAgentInformation(List buildAg log.debug("Sending message on topic {}: {}", channel, buildAgentInfo); websocketMessagingService.sendMessage(channel, buildAgentInfo); } + + /** + * Checks if the given destination is a build queue admin destination. + * This is the case if the destination is either /topic/admin/queued-jobs or /topic/admin/running-jobs. + * + * @param destination the destination to check + * @return true if the destination is a build queue admin destination, false otherwise + */ + public static boolean isBuildQueueAdminDestination(String destination) { + return "/topic/admin/queued-jobs".equals(destination) || "/topic/admin/running-jobs".equals(destination); + } + + /** + * Checks if the given destination is a build queue course destination. This is the case if the destination is either + * /topic/courses/{courseId}/queued-jobs or /topic/courses/{courseId}/running-jobs. + * If the destination is a build queue course destination, the courseId is returned. + * + * @param destination the destination to check + * @return the courseId if the destination is a build queue course destination, empty otherwise + */ + public static Optional isBuildQueueCourseDestination(String destination) { + // Define a pattern to match the expected course-related topic format + Matcher matcher = COURSE_DESTINATION_PATTERN.matcher(destination); + + // Check if the destination matches the pattern + if (matcher.matches()) { + // Extract the courseId from the matched groups + return Optional.of(Long.parseLong(matcher.group(1))); + } + return Optional.empty(); + } } From 70d1f58d97851ca23ef0ea423f890a8a7d6b0660 Mon Sep 17 00:00:00 2001 From: Stephan Krusche Date: Sun, 7 Jan 2024 16:30:36 +0100 Subject: [PATCH 19/25] Development: Improve server code --- .../artemis/domain/quiz/DragAndDropQuestion.java | 2 +- .../in/www1/artemis/domain/quiz/DragItem.java | 2 +- .../localvcci/LocalVCLocalCITestService.java | 2 +- src/test/resources/config/application.yml | 16 ++++++++-------- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/main/java/de/tum/in/www1/artemis/domain/quiz/DragAndDropQuestion.java b/src/main/java/de/tum/in/www1/artemis/domain/quiz/DragAndDropQuestion.java index a8c8e3ba5de8..5dc499a85dd5 100644 --- a/src/main/java/de/tum/in/www1/artemis/domain/quiz/DragAndDropQuestion.java +++ b/src/main/java/de/tum/in/www1/artemis/domain/quiz/DragAndDropQuestion.java @@ -177,7 +177,7 @@ public void onDelete() { } catch (FilePathParsingException e) { // if the file path is invalid, we don't need to delete it - log.warn("Could not delete file with path {}. Assume already deleted, entity can be removed.", backgroundFilePath, e); + log.warn("Could not delete file with path {}. Assume already deleted, DragAndDropQuestion {} can be removed.", backgroundFilePath, getId()); } } diff --git a/src/main/java/de/tum/in/www1/artemis/domain/quiz/DragItem.java b/src/main/java/de/tum/in/www1/artemis/domain/quiz/DragItem.java index a6de54e6dda4..ba58fafdd893 100644 --- a/src/main/java/de/tum/in/www1/artemis/domain/quiz/DragItem.java +++ b/src/main/java/de/tum/in/www1/artemis/domain/quiz/DragItem.java @@ -144,7 +144,7 @@ public void onDelete() { } catch (FilePathParsingException e) { // if the file path is invalid, we don't need to delete it - log.warn("Could not delete file with path {}. Assume already deleted, entity can be removed.", pictureFilePath, e); + log.warn("Could not delete file with path {}. Assume already deleted, DragAndDropQuestion {} can be removed.", pictureFilePath, getId()); } } diff --git a/src/test/java/de/tum/in/www1/artemis/localvcci/LocalVCLocalCITestService.java b/src/test/java/de/tum/in/www1/artemis/localvcci/LocalVCLocalCITestService.java index eb9c97386bc0..46178171aa3f 100644 --- a/src/test/java/de/tum/in/www1/artemis/localvcci/LocalVCLocalCITestService.java +++ b/src/test/java/de/tum/in/www1/artemis/localvcci/LocalVCLocalCITestService.java @@ -301,7 +301,7 @@ public String constructLocalVCUrl(String username, String projectKey, String rep * @return the URL to the repository. */ public String constructLocalVCUrl(String username, String password, String projectKey, String repositorySlug) { - return "http://" + username + (password.length() > 0 ? ":" : "") + password + (username.length() > 0 ? "@" : "") + "localhost:" + port + "/git/" + projectKey.toUpperCase() + return "http://" + username + (!password.isEmpty() ? ":" : "") + password + (!username.isEmpty() ? "@" : "") + "localhost:" + port + "/git/" + projectKey.toUpperCase() + "/" + repositorySlug + ".git"; } diff --git a/src/test/resources/config/application.yml b/src/test/resources/config/application.yml index 63dfc1650437..a6d5a4ed5499 100644 --- a/src/test/resources/config/application.yml +++ b/src/test/resources/config/application.yml @@ -175,22 +175,22 @@ zonky: enabled: true server: properties: - max_connections: 10 - shared_buffers: 1GB - effective_cache_size: 3GB + max_connections: 100 + shared_buffers: 2GB + effective_cache_size: 4GB maintenance_work_mem: 256MB checkpoint_completion_target: 0.9 wal_buffers: 16MB default_statistics_target: 100 random_page_cost: 1.1 effective_io_concurrency: 200 - work_mem: 26214kB + work_mem: 16MB min_wal_size: 1GB max_wal_size: 4GB - max_worker_processes: 4 - max_parallel_workers_per_gather: 2 - max_parallel_workers: 4 - max_parallel_maintenance_workers: 2 + max_worker_processes: 8 + max_parallel_workers_per_gather: 4 + max_parallel_workers: 8 + max_parallel_maintenance_workers: 4 mysql: docker: image: "mysql:8.2.0" From 5ccc89a897129a2b3ba3ff448a3cd105fbe95d46 Mon Sep 17 00:00:00 2001 From: Stephan Krusche Date: Sun, 7 Jan 2024 18:12:43 +0100 Subject: [PATCH 20/25] Development: Bump version to 6.7.5 --- README.md | 2 +- build.gradle | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index ae03cd573098..f6c63cf65786 100644 --- a/README.md +++ b/README.md @@ -155,7 +155,7 @@ Refer to [Using JHipster in production](http://www.jhipster.tech/production) for The following command can automate the deployment to a server. The example shows the deployment to the main Artemis test server (which runs a virtual machine): ```shell -./artemis-server-cli deploy username@artemistest.ase.in.tum.de -w build/libs/Artemis-6.7.4.war +./artemis-server-cli deploy username@artemistest.ase.in.tum.de -w build/libs/Artemis-6.7.5.war ``` ## Architecture diff --git a/build.gradle b/build.gradle index ceb9c53d68ed..b608ce2c6555 100644 --- a/build.gradle +++ b/build.gradle @@ -28,7 +28,7 @@ plugins { } group = "de.tum.in.www1.artemis" -version = "6.7.4" +version = "6.7.5" description = "Interactive Learning with Individual Feedback" java { diff --git a/package-lock.json b/package-lock.json index ab439fcf0dd3..a50aa6f662a6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "artemis", - "version": "6.7.4", + "version": "6.7.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "artemis", - "version": "6.7.4", + "version": "6.7.5", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index d030c2e3a876..0296a85238b9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "artemis", - "version": "6.7.4", + "version": "6.7.5", "description": "Interactive Learning with Individual Feedback", "private": true, "license": "MIT", From 2be8e25b12b91e2dc271380eacdd9de1177b52ac Mon Sep 17 00:00:00 2001 From: Stephan Krusche Date: Sun, 7 Jan 2024 20:34:50 +0100 Subject: [PATCH 21/25] Development: Fix method name in server code --- .../in/www1/artemis/web/rest/NotificationSettingsResource.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/NotificationSettingsResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/NotificationSettingsResource.java index f42e6a9fac7b..293ecdaf7cdb 100644 --- a/src/main/java/de/tum/in/www1/artemis/web/rest/NotificationSettingsResource.java +++ b/src/main/java/de/tum/in/www1/artemis/web/rest/NotificationSettingsResource.java @@ -101,7 +101,7 @@ public ResponseEntity saveNotificationSettingsForCurrentU */ @GetMapping("muted-conversations") @EnforceAtLeastStudent - public ResponseEntity> searchMembersOfConversation() { + public ResponseEntity> getMutedConversations() { User user = userRepository.getUser(); Set mutedConversations = notificationSettingRepository.findMutedConversations(user.getId()); return ResponseEntity.ok(mutedConversations); From 6e56d5f070405db5298c947e7e9611646c92e1bc Mon Sep 17 00:00:00 2001 From: Michal Kawka <73854755+coolchock@users.noreply.github.com> Date: Sun, 7 Jan 2024 22:43:38 +0100 Subject: [PATCH 22/25] Development: Fix e2e test 'Complaints about text exercises assessment' (#7857) --- src/test/cypress/e2e/exam/ExamAssessment.cy.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/cypress/e2e/exam/ExamAssessment.cy.ts b/src/test/cypress/e2e/exam/ExamAssessment.cy.ts index 1c4aeb6f7ab1..140b266f3239 100644 --- a/src/test/cypress/e2e/exam/ExamAssessment.cy.ts +++ b/src/test/cypress/e2e/exam/ExamAssessment.cy.ts @@ -171,7 +171,7 @@ function prepareExam(course: Course, end: dayjs.Dayjs, exerciseType: ExerciseTyp startDate: dayjs(), endDate: end, numberOfCorrectionRoundsInExam: 1, - examStudentReviewStart: resultDate.add(10, 'seconds'), + examStudentReviewStart: resultDate, examStudentReviewEnd: resultDate.add(1, 'minute'), publishResultsDate: resultDate, gracePeriod: 10, From 9f5fd993d5e186a2c71681d46dfde3df08c31669 Mon Sep 17 00:00:00 2001 From: Julian Christl Date: Mon, 8 Jan 2024 08:09:00 +0100 Subject: [PATCH 23/25] Development: Remove eager relationship between lecture and attachments (#7845) --- .../de/tum/in/www1/artemis/domain/Lecture.java | 6 +++--- .../artemis/repository/LectureRepository.java | 14 ++++++++------ .../artemis/service/LectureUnitService.java | 2 +- .../www1/artemis/web/rest/LectureResource.java | 6 +++--- .../rest/lecture/AttachmentUnitResource.java | 6 +++--- .../web/rest/lecture/ExerciseUnitResource.java | 2 +- .../web/rest/lecture/LectureUnitResource.java | 2 +- .../web/rest/lecture/OnlineUnitResource.java | 2 +- .../web/rest/lecture/TextUnitResource.java | 2 +- .../web/rest/lecture/VideoUnitResource.java | 2 +- .../lecture/AttachmentUnitIntegrationTest.java | 7 ++++--- .../lecture/LectureIntegrationTest.java | 2 +- .../lecture/LectureUnitIntegrationTest.java | 12 ++++++------ .../artemis/lecture/LectureUtilService.java | 2 +- .../lecture/OnlineUnitIntegrationTest.java | 18 +++++++++--------- .../lecture/TextUnitIntegrationTest.java | 6 +++--- .../lecture/VideoUnitIntegrationTest.java | 16 ++++++++-------- .../service/LectureImportServiceTest.java | 4 ++-- 18 files changed, 57 insertions(+), 54 deletions(-) diff --git a/src/main/java/de/tum/in/www1/artemis/domain/Lecture.java b/src/main/java/de/tum/in/www1/artemis/domain/Lecture.java index 23696199cc6e..9985cc3a3f0c 100644 --- a/src/main/java/de/tum/in/www1/artemis/domain/Lecture.java +++ b/src/main/java/de/tum/in/www1/artemis/domain/Lecture.java @@ -39,17 +39,17 @@ public class Lecture extends DomainObject { @Column(name = "visible_date") private ZonedDateTime visibleDate; - @OneToMany(mappedBy = "lecture", cascade = CascadeType.REMOVE, orphanRemoval = true, fetch = FetchType.EAGER) + @OneToMany(mappedBy = "lecture", cascade = CascadeType.REMOVE, orphanRemoval = true) @JsonIgnoreProperties(value = "lecture", allowSetters = true) private Set attachments = new HashSet<>(); - @OneToMany(mappedBy = "lecture", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY) + @OneToMany(mappedBy = "lecture", cascade = CascadeType.ALL, orphanRemoval = true) @OrderColumn(name = "lecture_unit_order") @JsonIgnoreProperties("lecture") @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) private List lectureUnits = new ArrayList<>(); - @OneToMany(mappedBy = "lecture", cascade = CascadeType.REMOVE, orphanRemoval = true, fetch = FetchType.LAZY) + @OneToMany(mappedBy = "lecture", cascade = CascadeType.REMOVE, orphanRemoval = true) @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) @JsonIncludeProperties({ "id" }) private Set posts = new HashSet<>(); diff --git a/src/main/java/de/tum/in/www1/artemis/repository/LectureRepository.java b/src/main/java/de/tum/in/www1/artemis/repository/LectureRepository.java index 1c57a0a774bf..3c7b862d3432 100644 --- a/src/main/java/de/tum/in/www1/artemis/repository/LectureRepository.java +++ b/src/main/java/de/tum/in/www1/artemis/repository/LectureRepository.java @@ -62,6 +62,7 @@ public interface LectureRepository extends JpaRepository { @Query(""" SELECT lecture FROM Lecture lecture + LEFT JOIN FETCH lecture.attachments LEFT JOIN FETCH lecture.posts LEFT JOIN FETCH lecture.lectureUnits lu LEFT JOIN FETCH lu.completedUsers cu @@ -70,7 +71,7 @@ public interface LectureRepository extends JpaRepository { LEFT JOIN FETCH exercise.competencies WHERE lecture.id = :lectureId """) - Optional findByIdWithPostsAndLectureUnitsAndCompetenciesAndCompletions(@Param("lectureId") Long lectureId); + Optional findByIdWithAttachmentsAndPostsAndLectureUnitsAndCompetenciesAndCompletions(@Param("lectureId") Long lectureId); @Query(""" SELECT lecture @@ -87,9 +88,10 @@ public interface LectureRepository extends JpaRepository { SELECT lecture FROM Lecture lecture LEFT JOIN FETCH lecture.lectureUnits + LEFT JOIN FETCH lecture.attachments WHERE lecture.id = :lectureId """) - Optional findByIdWithLectureUnits(@Param("lectureId") Long lectureId); + Optional findByIdWithLectureUnitsAndAttachments(@Param("lectureId") Long lectureId); @Query(""" SELECT lecture @@ -148,13 +150,13 @@ default Lecture findByIdWithLectureUnitsAndCompetenciesElseThrow(Long lectureId) } @NotNull - default Lecture findByIdWithPostsAndLectureUnitsAndCompetenciesAndCompletionsElseThrow(Long lectureId) { - return findByIdWithPostsAndLectureUnitsAndCompetenciesAndCompletions(lectureId).orElseThrow(() -> new EntityNotFoundException("Lecture", lectureId)); + default Lecture findByIdWithAttachmentsAndPostsAndLectureUnitsAndCompetenciesAndCompletionsElseThrow(Long lectureId) { + return findByIdWithAttachmentsAndPostsAndLectureUnitsAndCompetenciesAndCompletions(lectureId).orElseThrow(() -> new EntityNotFoundException("Lecture", lectureId)); } @NotNull - default Lecture findByIdWithLectureUnitsElseThrow(Long lectureId) { - return findByIdWithLectureUnits(lectureId).orElseThrow(() -> new EntityNotFoundException("Lecture", lectureId)); + default Lecture findByIdWithLectureUnitsAndAttachmentsElseThrow(Long lectureId) { + return findByIdWithLectureUnitsAndAttachments(lectureId).orElseThrow(() -> new EntityNotFoundException("Lecture", lectureId)); } @NotNull diff --git a/src/main/java/de/tum/in/www1/artemis/service/LectureUnitService.java b/src/main/java/de/tum/in/www1/artemis/service/LectureUnitService.java index 2474419f9985..b02b968cef08 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/LectureUnitService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/LectureUnitService.java @@ -88,7 +88,7 @@ public void removeLectureUnit(@NotNull LectureUnit lectureUnit) { }).toList()); } - Lecture lecture = lectureRepository.findByIdWithLectureUnitsElseThrow(lectureUnitToDelete.getLecture().getId()); + Lecture lecture = lectureRepository.findByIdWithLectureUnitsAndAttachmentsElseThrow(lectureUnitToDelete.getLecture().getId()); // Creating a new list of lecture units without the one we want to remove List lectureUnitsUpdated = new ArrayList<>(); for (LectureUnit unit : lecture.getLectureUnits()) { diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/LectureResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/LectureResource.java index 2a43ac61cba5..a03ab28511fb 100644 --- a/src/main/java/de/tum/in/www1/artemis/web/rest/LectureResource.java +++ b/src/main/java/de/tum/in/www1/artemis/web/rest/LectureResource.java @@ -122,7 +122,7 @@ public ResponseEntity updateLecture(@RequestBody Lecture lecture) { authCheckService.checkHasAtLeastRoleInCourseElseThrow(Role.EDITOR, lecture.getCourse(), null); // Make sure that the original references are preserved. - Lecture originalLecture = lectureRepository.findByIdWithLectureUnitsElseThrow(lecture.getId()); + Lecture originalLecture = lectureRepository.findByIdWithLectureUnitsAndAttachmentsElseThrow(lecture.getId()); // NOTE: Make sure that all references are preserved here lecture.setLectureUnits(originalLecture.getLectureUnits()); @@ -228,7 +228,7 @@ public ResponseEntity getLecture(@PathVariable Long lectureId) { @EnforceAtLeastEditor public ResponseEntity importLecture(@PathVariable long sourceLectureId, @RequestParam long courseId) throws URISyntaxException { final var user = userRepository.getUserWithGroupsAndAuthorities(); - final var sourceLecture = lectureRepository.findByIdWithLectureUnitsElseThrow(sourceLectureId); + final var sourceLecture = lectureRepository.findByIdWithLectureUnitsAndAttachmentsElseThrow(sourceLectureId); final var destinationCourse = courseRepository.findByIdWithLecturesElseThrow(courseId); Course course = sourceLecture.getCourse(); @@ -256,7 +256,7 @@ public ResponseEntity importLecture(@PathVariable long sourceLectureId, @EnforceAtLeastStudent public ResponseEntity getLectureWithDetails(@PathVariable Long lectureId) { log.debug("REST request to get lecture {} with details", lectureId); - Lecture lecture = lectureRepository.findByIdWithPostsAndLectureUnitsAndCompetenciesAndCompletionsElseThrow(lectureId); + Lecture lecture = lectureRepository.findByIdWithAttachmentsAndPostsAndLectureUnitsAndCompetenciesAndCompletionsElseThrow(lectureId); Course course = lecture.getCourse(); if (course == null) { return ResponseEntity.badRequest().build(); diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/lecture/AttachmentUnitResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/lecture/AttachmentUnitResource.java index d9a23c2cf7f4..a076a72741d2 100644 --- a/src/main/java/de/tum/in/www1/artemis/web/rest/lecture/AttachmentUnitResource.java +++ b/src/main/java/de/tum/in/www1/artemis/web/rest/lecture/AttachmentUnitResource.java @@ -146,7 +146,7 @@ public ResponseEntity createAttachmentUnit(@PathVariable Long le throw new BadRequestAlertException("A new attachment cannot already have an ID", ENTITY_NAME, "idexists"); } - Lecture lecture = lectureRepository.findByIdWithLectureUnitsElseThrow(lectureId); + Lecture lecture = lectureRepository.findByIdWithLectureUnitsAndAttachmentsElseThrow(lectureId); if (lecture.getCourse() == null) { throw new ConflictException("Specified lecture is not part of a course", "AttachmentUnit", "courseMissing"); } @@ -211,7 +211,7 @@ public ResponseEntity> createAttachmentUnits(@PathVariable try { byte[] fileBytes = fileService.getFileForPath(filePath); List savedAttachmentUnits = lectureUnitProcessingService.splitAndSaveUnits(lectureUnitInformationDTO, fileBytes, - lectureRepository.findByIdWithLectureUnitsElseThrow(lectureId)); + lectureRepository.findByIdWithLectureUnitsAndAttachmentsElseThrow(lectureId)); savedAttachmentUnits.forEach(attachmentUnitService::prepareAttachmentUnitForClient); savedAttachmentUnits.forEach(competencyProgressService::updateProgressByLearningObjectAsync); return ResponseEntity.ok().body(savedAttachmentUnits); @@ -297,7 +297,7 @@ private void checkAttachmentUnitCourseAndLecture(AttachmentUnit attachmentUnit, * @param lectureId The id of the lecture */ private void checkLecture(Long lectureId) { - Lecture lecture = lectureRepository.findByIdWithLectureUnitsElseThrow(lectureId); + Lecture lecture = lectureRepository.findByIdWithLectureUnitsAndAttachmentsElseThrow(lectureId); if (lecture.getCourse() == null) { throw new ConflictException("Specified lecture is not part of a course", "AttachmentUnit", "courseMissing"); } diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/lecture/ExerciseUnitResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/lecture/ExerciseUnitResource.java index dd28d692e564..e854bbe7a0b5 100644 --- a/src/main/java/de/tum/in/www1/artemis/web/rest/lecture/ExerciseUnitResource.java +++ b/src/main/java/de/tum/in/www1/artemis/web/rest/lecture/ExerciseUnitResource.java @@ -59,7 +59,7 @@ public ResponseEntity createExerciseUnit(@PathVariable Long lectur if (exerciseUnit.getId() != null) { throw new BadRequestException(); } - Lecture lecture = lectureRepository.findByIdWithLectureUnitsElseThrow(lectureId); + Lecture lecture = lectureRepository.findByIdWithLectureUnitsAndAttachmentsElseThrow(lectureId); if (lecture.getCourse() == null) { throw new ConflictException("Specified lecture is not part of a course", "ExerciseUnit", "courseMissing"); } diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/lecture/LectureUnitResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/lecture/LectureUnitResource.java index 57fbd7abb92d..756d1d6646a5 100644 --- a/src/main/java/de/tum/in/www1/artemis/web/rest/lecture/LectureUnitResource.java +++ b/src/main/java/de/tum/in/www1/artemis/web/rest/lecture/LectureUnitResource.java @@ -71,7 +71,7 @@ public LectureUnitResource(AuthorizationCheckService authorizationCheckService, @EnforceAtLeastEditor public ResponseEntity> updateLectureUnitsOrder(@PathVariable Long lectureId, @RequestBody List orderedLectureUnitIds) { log.debug("REST request to update the order of lecture units of lecture: {}", lectureId); - final Lecture lecture = lectureRepository.findByIdWithLectureUnitsElseThrow(lectureId); + final Lecture lecture = lectureRepository.findByIdWithLectureUnitsAndAttachmentsElseThrow(lectureId); if (lecture.getCourse() == null) { throw new ConflictException("Specified lecture is not part of a course", "LectureUnit", "courseMissing"); diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/lecture/OnlineUnitResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/lecture/OnlineUnitResource.java index 68daa47b01e7..78b8abcaaf3f 100644 --- a/src/main/java/de/tum/in/www1/artemis/web/rest/lecture/OnlineUnitResource.java +++ b/src/main/java/de/tum/in/www1/artemis/web/rest/lecture/OnlineUnitResource.java @@ -112,7 +112,7 @@ public ResponseEntity createOnlineUnit(@PathVariable Long lectureId, validateUrl(onlineUnit); - Lecture lecture = lectureRepository.findByIdWithLectureUnitsElseThrow(lectureId); + Lecture lecture = lectureRepository.findByIdWithLectureUnitsAndAttachmentsElseThrow(lectureId); if (lecture.getCourse() == null) { throw new ConflictException("Specified lecture is not part of a course", "onlineUnit", "courseMissing"); } diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/lecture/TextUnitResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/lecture/TextUnitResource.java index c33105f06780..e61fca37614f 100644 --- a/src/main/java/de/tum/in/www1/artemis/web/rest/lecture/TextUnitResource.java +++ b/src/main/java/de/tum/in/www1/artemis/web/rest/lecture/TextUnitResource.java @@ -119,7 +119,7 @@ public ResponseEntity createTextUnit(@PathVariable Long lectureId, @Re throw new BadRequestAlertException("A new text unit cannot have an id", ENTITY_NAME, "idExists"); } - Lecture lecture = lectureRepository.findByIdWithLectureUnitsElseThrow(lectureId); + Lecture lecture = lectureRepository.findByIdWithLectureUnitsAndAttachmentsElseThrow(lectureId); if (lecture.getCourse() == null || (textUnit.getLecture() != null && !lecture.getId().equals(textUnit.getLecture().getId()))) { throw new ConflictException("Input data not valid", ENTITY_NAME, "inputInvalid"); diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/lecture/VideoUnitResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/lecture/VideoUnitResource.java index 01d39701aaea..7b85b5f51dc5 100644 --- a/src/main/java/de/tum/in/www1/artemis/web/rest/lecture/VideoUnitResource.java +++ b/src/main/java/de/tum/in/www1/artemis/web/rest/lecture/VideoUnitResource.java @@ -113,7 +113,7 @@ public ResponseEntity createVideoUnit(@PathVariable Long lectureId, @ normalizeVideoUrl(videoUnit); validateVideoUrl(videoUnit); - Lecture lecture = lectureRepository.findByIdWithLectureUnitsElseThrow(lectureId); + Lecture lecture = lectureRepository.findByIdWithLectureUnitsAndAttachmentsElseThrow(lectureId); if (lecture.getCourse() == null) { throw new ConflictException("Specified lecture is not part of a course", "VideoUnit", "courseMissing"); } diff --git a/src/test/java/de/tum/in/www1/artemis/lecture/AttachmentUnitIntegrationTest.java b/src/test/java/de/tum/in/www1/artemis/lecture/AttachmentUnitIntegrationTest.java index a3fc6629cd7f..e08d442064df 100644 --- a/src/test/java/de/tum/in/www1/artemis/lecture/AttachmentUnitIntegrationTest.java +++ b/src/test/java/de/tum/in/www1/artemis/lecture/AttachmentUnitIntegrationTest.java @@ -276,16 +276,17 @@ void updateAttachmentUnit_asInstructor_shouldKeepOrdering() throws Exception { request.getMvc().perform(buildUpdateAttachmentUnit(attachmentUnit, attachment)).andExpect(status().isOk()); SecurityUtils.setAuthorizationObject(); - List updatedOrderedUnits = lectureRepository.findByIdWithLectureUnits(lecture1.getId()).orElseThrow().getLectureUnits(); + List updatedOrderedUnits = lectureRepository.findByIdWithLectureUnitsAndAttachments(lecture1.getId()).orElseThrow().getLectureUnits(); assertThat(updatedOrderedUnits).containsExactlyElementsOf(orderedUnits); } private void persistAttachmentUnitWithLecture() { this.attachmentUnit = attachmentUnitRepository.saveAndFlush(this.attachmentUnit); - lecture1 = lectureRepository.findByIdWithLectureUnits(lecture1.getId()).orElseThrow(); + lecture1 = lectureRepository.findByIdWithLectureUnitsAndAttachments(lecture1.getId()).orElseThrow(); lecture1.addLectureUnit(this.attachmentUnit); lecture1 = lectureRepository.saveAndFlush(lecture1); - this.attachmentUnit = (AttachmentUnit) lectureRepository.findByIdWithLectureUnits(lecture1.getId()).orElseThrow().getLectureUnits().stream().findFirst().orElseThrow(); + this.attachmentUnit = (AttachmentUnit) lectureRepository.findByIdWithLectureUnitsAndAttachments(lecture1.getId()).orElseThrow().getLectureUnits().stream().findFirst() + .orElseThrow(); } @Test diff --git a/src/test/java/de/tum/in/www1/artemis/lecture/LectureIntegrationTest.java b/src/test/java/de/tum/in/www1/artemis/lecture/LectureIntegrationTest.java index c9b93a0fcb51..4aaf1dfb6993 100644 --- a/src/test/java/de/tum/in/www1/artemis/lecture/LectureIntegrationTest.java +++ b/src/test/java/de/tum/in/www1/artemis/lecture/LectureIntegrationTest.java @@ -208,7 +208,7 @@ void updateLecture_correctRequestBody_shouldUpdateLecture() throws Exception { @Test @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") void updateLecture_NoId_shouldReturnBadRequest() throws Exception { - Lecture originalLecture = lectureRepository.findByIdWithLectureUnits(lecture1.getId()).orElseThrow(); + Lecture originalLecture = lectureRepository.findByIdWithLectureUnitsAndAttachments(lecture1.getId()).orElseThrow(); originalLecture.setId(null); originalLecture.setChannelName("test"); diff --git a/src/test/java/de/tum/in/www1/artemis/lecture/LectureUnitIntegrationTest.java b/src/test/java/de/tum/in/www1/artemis/lecture/LectureUnitIntegrationTest.java index 4a89a196cbdd..efb240a092b0 100644 --- a/src/test/java/de/tum/in/www1/artemis/lecture/LectureUnitIntegrationTest.java +++ b/src/test/java/de/tum/in/www1/artemis/lecture/LectureUnitIntegrationTest.java @@ -83,7 +83,7 @@ void initTestCase() throws Exception { lectureUtilService.addLectureUnitsToLecture(course1.getLectures().stream().skip(1).findFirst().orElseThrow(), List.of(textUnit2)); this.lecture1 = lectureUtilService.addLectureUnitsToLecture(this.lecture1, List.of(this.textUnit, onlineUnit, attachmentUnit)); - this.lecture1 = lectureRepository.findByIdWithLectureUnitsElseThrow(lecture1.getId()); + this.lecture1 = lectureRepository.findByIdWithLectureUnitsAndAttachmentsElseThrow(lecture1.getId()); this.textUnit = textUnitRepository.findById(this.textUnit.getId()).orElseThrow(); this.textUnit2 = textUnitRepository.findById(textUnit2.getId()).orElseThrow(); } @@ -110,7 +110,7 @@ private void testAllPreAuthorize() throws Exception { void deleteLectureUnit() throws Exception { var lectureUnitId = lecture1.getLectureUnits().get(0).getId(); request.delete("/api/lectures/" + lecture1.getId() + "/lecture-units/" + lectureUnitId, HttpStatus.OK); - this.lecture1 = lectureRepository.findByIdWithLectureUnitsElseThrow(lecture1.getId()); + this.lecture1 = lectureRepository.findByIdWithLectureUnitsAndAttachmentsElseThrow(lecture1.getId()); assertThat(this.lecture1.getLectureUnits().stream().map(DomainObject::getId)).doesNotContain(lectureUnitId); } @@ -126,7 +126,7 @@ void deleteLectureUnit_shouldUnlinkCompetency() throws Exception { assertThat(lecture.getLectureUnits().get(0).getCompetencies()).isNotEmpty(); request.delete("/api/lectures/" + lecture1.getId() + "/lecture-units/" + lectureUnit.getId(), HttpStatus.OK); - this.lecture1 = lectureRepository.findByIdWithLectureUnitsElseThrow(lecture1.getId()); + this.lecture1 = lectureRepository.findByIdWithLectureUnitsAndAttachmentsElseThrow(lecture1.getId()); assertThat(this.lecture1.getLectureUnits().stream().map(DomainObject::getId)).doesNotContain(lectureUnit.getId()); } @@ -146,7 +146,7 @@ void deleteLectureUnit_shouldRemoveCompletions() throws Exception { request.delete("/api/lectures/" + lecture1.getId() + "/lecture-units/" + lectureUnit.getId(), HttpStatus.OK); - this.lecture1 = lectureRepository.findByIdWithLectureUnitsElseThrow(lecture1.getId()); + this.lecture1 = lectureRepository.findByIdWithLectureUnitsAndAttachmentsElseThrow(lecture1.getId()); assertThat(this.lecture1.getLectureUnits().stream().map(DomainObject::getId)).doesNotContain(lectureUnit.getId()); assertThat(lectureUnitCompletionRepository.findByLectureUnitIdAndUserId(lectureUnit.getId(), user.getId())).isEmpty(); } @@ -226,7 +226,7 @@ void setLectureUnitCompletion() throws Exception { request.postWithoutLocation("/api/lectures/" + lecture1.getId() + "/lecture-units/" + lecture1.getLectureUnits().get(0).getId() + "/completion?completed=true", null, HttpStatus.OK, null); - this.lecture1 = lectureRepository.findByIdWithPostsAndLectureUnitsAndCompetenciesAndCompletionsElseThrow(lecture1.getId()); + this.lecture1 = lectureRepository.findByIdWithAttachmentsAndPostsAndLectureUnitsAndCompetenciesAndCompletionsElseThrow(lecture1.getId()); LectureUnit lectureUnit = this.lecture1.getLectureUnits().get(0); assertThat(lectureUnit.getCompletedUsers()).isNotEmpty(); @@ -237,7 +237,7 @@ void setLectureUnitCompletion() throws Exception { request.postWithoutLocation("/api/lectures/" + lecture1.getId() + "/lecture-units/" + lecture1.getLectureUnits().get(0).getId() + "/completion?completed=false", null, HttpStatus.OK, null); - this.lecture1 = lectureRepository.findByIdWithPostsAndLectureUnitsAndCompetenciesAndCompletionsElseThrow(lecture1.getId()); + this.lecture1 = lectureRepository.findByIdWithAttachmentsAndPostsAndLectureUnitsAndCompetenciesAndCompletionsElseThrow(lecture1.getId()); lectureUnit = this.lecture1.getLectureUnits().get(0); assertThat(lectureUnit.getCompletedUsers()).isEmpty(); diff --git a/src/test/java/de/tum/in/www1/artemis/lecture/LectureUtilService.java b/src/test/java/de/tum/in/www1/artemis/lecture/LectureUtilService.java index f7c5984843d3..918aef05f7e4 100644 --- a/src/test/java/de/tum/in/www1/artemis/lecture/LectureUtilService.java +++ b/src/test/java/de/tum/in/www1/artemis/lecture/LectureUtilService.java @@ -160,7 +160,7 @@ public Lecture addCompetencyToLectureUnits(Lecture lecture, Set comp * @return The updated Lecture */ public Lecture addLectureUnitsToLecture(Lecture lecture, List lectureUnits) { - Lecture l = lectureRepo.findByIdWithLectureUnits(lecture.getId()).orElseThrow(); + Lecture l = lectureRepo.findByIdWithLectureUnitsAndAttachments(lecture.getId()).orElseThrow(); for (LectureUnit lectureUnit : lectureUnits) { l.addLectureUnit(lectureUnit); } diff --git a/src/test/java/de/tum/in/www1/artemis/lecture/OnlineUnitIntegrationTest.java b/src/test/java/de/tum/in/www1/artemis/lecture/OnlineUnitIntegrationTest.java index a09199424c15..ddfda71753ae 100644 --- a/src/test/java/de/tum/in/www1/artemis/lecture/OnlineUnitIntegrationTest.java +++ b/src/test/java/de/tum/in/www1/artemis/lecture/OnlineUnitIntegrationTest.java @@ -125,7 +125,7 @@ void createOnlineUnit_withId_shouldReturnBadRequest() throws Exception { void updateOnlineUnit_asInstructor_shouldUpdateOnlineUnit() throws Exception { persistOnlineUnitWithLecture(); - this.onlineUnit = (OnlineUnit) lectureRepository.findByIdWithLectureUnitsElseThrow(lecture1.getId()).getLectureUnits().stream().findFirst().orElseThrow(); + this.onlineUnit = (OnlineUnit) lectureRepository.findByIdWithLectureUnitsAndAttachmentsElseThrow(lecture1.getId()).getLectureUnits().stream().findFirst().orElseThrow(); this.onlineUnit.setSource("https://www.youtube.com/embed/8iU8LPEa4o0"); this.onlineUnit.setDescription("Changed"); this.onlineUnit = request.putWithResponseBody("/api/lectures/" + lecture1.getId() + "/online-units", this.onlineUnit, OnlineUnit.class, HttpStatus.OK); @@ -147,7 +147,7 @@ void updateOnlineUnit_asInstructor_shouldKeepOrdering() throws Exception { // Updating the lecture unit should not change order attribute request.putWithResponseBody("/api/lectures/" + lecture1.getId() + "/online-units", onlineUnit, OnlineUnit.class, HttpStatus.OK); - List updatedOrderedUnits = lectureRepository.findByIdWithLectureUnits(lecture1.getId()).orElseThrow().getLectureUnits(); + List updatedOrderedUnits = lectureRepository.findByIdWithLectureUnitsAndAttachments(lecture1.getId()).orElseThrow().getLectureUnits(); assertThat(updatedOrderedUnits).containsExactlyElementsOf(orderedUnits); } @@ -155,7 +155,7 @@ private void persistOnlineUnitWithLecture() { this.onlineUnit = onlineUnitRepository.save(this.onlineUnit); lecture1.addLectureUnit(this.onlineUnit); lecture1 = lectureRepository.save(lecture1); - this.onlineUnit = (OnlineUnit) lectureRepository.findByIdWithLectureUnitsElseThrow(lecture1.getId()).getLectureUnits().stream().findFirst().orElseThrow(); + this.onlineUnit = (OnlineUnit) lectureRepository.findByIdWithLectureUnitsAndAttachmentsElseThrow(lecture1.getId()).getLectureUnits().stream().findFirst().orElseThrow(); } @Test @@ -163,7 +163,7 @@ private void persistOnlineUnitWithLecture() { void updateOnlineUnit_InstructorNotInCourse_shouldReturnForbidden() throws Exception { persistOnlineUnitWithLecture(); - this.onlineUnit = (OnlineUnit) lectureRepository.findByIdWithLectureUnitsElseThrow(lecture1.getId()).getLectureUnits().stream().findFirst().orElseThrow(); + this.onlineUnit = (OnlineUnit) lectureRepository.findByIdWithLectureUnitsAndAttachmentsElseThrow(lecture1.getId()).getLectureUnits().stream().findFirst().orElseThrow(); this.onlineUnit.setDescription("Changed"); this.onlineUnit.setSource("https://www.youtube.com/embed/8iU8LPEa4o0"); this.onlineUnit = request.putWithResponseBody("/api/lectures/" + lecture1.getId() + "/online-units", this.onlineUnit, OnlineUnit.class, HttpStatus.FORBIDDEN); @@ -174,7 +174,7 @@ void updateOnlineUnit_InstructorNotInCourse_shouldReturnForbidden() throws Excep void updateOnlineUnit_noId_shouldReturnBadRequest() throws Exception { persistOnlineUnitWithLecture(); - this.onlineUnit = (OnlineUnit) lectureRepository.findByIdWithLectureUnitsElseThrow(lecture1.getId()).getLectureUnits().stream().findFirst().orElseThrow(); + this.onlineUnit = (OnlineUnit) lectureRepository.findByIdWithLectureUnitsAndAttachmentsElseThrow(lecture1.getId()).getLectureUnits().stream().findFirst().orElseThrow(); this.onlineUnit.setId(null); this.onlineUnit = request.putWithResponseBody("/api/lectures/" + lecture1.getId() + "/online-units", this.onlineUnit, OnlineUnit.class, HttpStatus.BAD_REQUEST); } @@ -184,7 +184,7 @@ void updateOnlineUnit_noId_shouldReturnBadRequest() throws Exception { void updateOnlineUnit_noLecture_shouldReturnConflict() throws Exception { persistOnlineUnitWithLecture(); - this.onlineUnit = (OnlineUnit) lectureRepository.findByIdWithLectureUnitsElseThrow(lecture1.getId()).getLectureUnits().stream().findFirst().orElseThrow(); + this.onlineUnit = (OnlineUnit) lectureRepository.findByIdWithLectureUnitsAndAttachmentsElseThrow(lecture1.getId()).getLectureUnits().stream().findFirst().orElseThrow(); this.onlineUnit.setLecture(null); this.onlineUnit = request.putWithResponseBody("/api/lectures/" + lecture1.getId() + "/online-units", this.onlineUnit, OnlineUnit.class, HttpStatus.CONFLICT); } @@ -194,7 +194,7 @@ void updateOnlineUnit_noLecture_shouldReturnConflict() throws Exception { void getOnlineUnit_correctId_shouldReturnOnlineUnit() throws Exception { persistOnlineUnitWithLecture(); - this.onlineUnit = (OnlineUnit) lectureRepository.findByIdWithLectureUnitsElseThrow(lecture1.getId()).getLectureUnits().stream().findFirst().orElseThrow(); + this.onlineUnit = (OnlineUnit) lectureRepository.findByIdWithLectureUnitsAndAttachmentsElseThrow(lecture1.getId()).getLectureUnits().stream().findFirst().orElseThrow(); OnlineUnit onlineUnitFromRequest = request.get("/api/lectures/" + lecture1.getId() + "/online-units/" + this.onlineUnit.getId(), HttpStatus.OK, OnlineUnit.class); assertThat(this.onlineUnit.getId()).isEqualTo(onlineUnitFromRequest.getId()); } @@ -204,7 +204,7 @@ void getOnlineUnit_correctId_shouldReturnOnlineUnit() throws Exception { void getOnlineUnit_incorrectId_shouldReturnConflict() throws Exception { persistOnlineUnitWithLecture(); - this.onlineUnit = (OnlineUnit) lectureRepository.findByIdWithLectureUnitsElseThrow(lecture1.getId()).getLectureUnits().stream().findFirst().orElseThrow(); + this.onlineUnit = (OnlineUnit) lectureRepository.findByIdWithLectureUnitsAndAttachmentsElseThrow(lecture1.getId()).getLectureUnits().stream().findFirst().orElseThrow(); request.get("/api/lectures/" + "999" + "/online-units/" + this.onlineUnit.getId(), HttpStatus.CONFLICT, OnlineUnit.class); } @@ -241,7 +241,7 @@ void getOnlineResource_malformedUrl(String link) throws Exception { void deleteOnlineUnit_correctId_shouldDeleteOnlineUnit() throws Exception { persistOnlineUnitWithLecture(); - this.onlineUnit = (OnlineUnit) lectureRepository.findByIdWithLectureUnitsElseThrow(lecture1.getId()).getLectureUnits().stream().findFirst().orElseThrow(); + this.onlineUnit = (OnlineUnit) lectureRepository.findByIdWithLectureUnitsAndAttachmentsElseThrow(lecture1.getId()).getLectureUnits().stream().findFirst().orElseThrow(); assertThat(this.onlineUnit.getId()).isNotNull(); request.delete("/api/lectures/" + lecture1.getId() + "/lecture-units/" + this.onlineUnit.getId(), HttpStatus.OK); request.get("/api/lectures/" + lecture1.getId() + "/online-units/" + this.onlineUnit.getId(), HttpStatus.NOT_FOUND, OnlineUnit.class); diff --git a/src/test/java/de/tum/in/www1/artemis/lecture/TextUnitIntegrationTest.java b/src/test/java/de/tum/in/www1/artemis/lecture/TextUnitIntegrationTest.java index 09715dba3702..08664599d9da 100644 --- a/src/test/java/de/tum/in/www1/artemis/lecture/TextUnitIntegrationTest.java +++ b/src/test/java/de/tum/in/www1/artemis/lecture/TextUnitIntegrationTest.java @@ -115,7 +115,7 @@ void updateTextUnit_asEditor_shouldKeepOrdering() throws Exception { // Updating the lecture unit should not change order attribute request.putWithResponseBody("/api/lectures/" + lecture.getId() + "/text-units", textUnit, TextUnit.class, HttpStatus.OK); - List updatedOrderedUnits = lectureRepository.findByIdWithLectureUnits(lecture.getId()).orElseThrow().getLectureUnits(); + List updatedOrderedUnits = lectureRepository.findByIdWithLectureUnitsAndAttachments(lecture.getId()).orElseThrow().getLectureUnits(); assertThat(updatedOrderedUnits).containsExactlyElementsOf(orderedUnits); } @@ -149,9 +149,9 @@ void deleteTextUnit_correctId_shouldDeleteTextUnit() throws Exception { private void persistTextUnitWithLecture() { this.textUnit = textUnitRepository.save(this.textUnit); - lecture = lectureRepository.findByIdWithLectureUnits(lecture.getId()).orElseThrow(); + lecture = lectureRepository.findByIdWithLectureUnitsAndAttachments(lecture.getId()).orElseThrow(); lecture.addLectureUnit(this.textUnit); lecture = lectureRepository.save(lecture); - this.textUnit = (TextUnit) lectureRepository.findByIdWithLectureUnits(lecture.getId()).orElseThrow().getLectureUnits().stream().findFirst().orElseThrow(); + this.textUnit = (TextUnit) lectureRepository.findByIdWithLectureUnitsAndAttachments(lecture.getId()).orElseThrow().getLectureUnits().stream().findFirst().orElseThrow(); } } diff --git a/src/test/java/de/tum/in/www1/artemis/lecture/VideoUnitIntegrationTest.java b/src/test/java/de/tum/in/www1/artemis/lecture/VideoUnitIntegrationTest.java index aea03a64a163..5af91e419aee 100644 --- a/src/test/java/de/tum/in/www1/artemis/lecture/VideoUnitIntegrationTest.java +++ b/src/test/java/de/tum/in/www1/artemis/lecture/VideoUnitIntegrationTest.java @@ -105,7 +105,7 @@ void createVideoUnit_InstructorNotInCourse_shouldReturnForbidden() throws Except void updateVideoUnit_asInstructor_shouldUpdateVideoUnit() throws Exception { persistVideoUnitWithLecture(); - this.videoUnit = (VideoUnit) lectureRepository.findByIdWithLectureUnits(lecture1.getId()).orElseThrow().getLectureUnits().stream().findFirst().orElseThrow(); + this.videoUnit = (VideoUnit) lectureRepository.findByIdWithLectureUnitsAndAttachments(lecture1.getId()).orElseThrow().getLectureUnits().stream().findFirst().orElseThrow(); this.videoUnit.setSource("https://www.youtube.com/embed/8iU8LPEa4o0"); this.videoUnit.setDescription("Changed"); this.videoUnit = request.putWithResponseBody("/api/lectures/" + lecture1.getId() + "/video-units", this.videoUnit, VideoUnit.class, HttpStatus.OK); @@ -117,7 +117,7 @@ void updateVideoUnit_asInstructor_shouldUpdateVideoUnit() throws Exception { void updateVideoUnit_withoutLecture_shouldReturnConflict() throws Exception { persistVideoUnitWithLecture(); - this.videoUnit = (VideoUnit) lectureRepository.findByIdWithLectureUnits(lecture1.getId()).orElseThrow().getLectureUnits().stream().findFirst().orElseThrow(); + this.videoUnit = (VideoUnit) lectureRepository.findByIdWithLectureUnitsAndAttachments(lecture1.getId()).orElseThrow().getLectureUnits().stream().findFirst().orElseThrow(); this.videoUnit.setLecture(null); request.putWithResponseBody("/api/lectures/" + lecture1.getId() + "/video-units", this.videoUnit, VideoUnit.class, HttpStatus.CONFLICT); } @@ -137,7 +137,7 @@ void updateVideoUnit_asInstructor_shouldKeepOrdering() throws Exception { // Updating the lecture unit should not change order attribute request.putWithResponseBody("/api/lectures/" + lecture1.getId() + "/video-units", videoUnit, VideoUnit.class, HttpStatus.OK); - List updatedOrderedUnits = lectureRepository.findByIdWithLectureUnits(lecture1.getId()).orElseThrow().getLectureUnits(); + List updatedOrderedUnits = lectureRepository.findByIdWithLectureUnitsAndAttachments(lecture1.getId()).orElseThrow().getLectureUnits(); assertThat(updatedOrderedUnits).containsExactlyElementsOf(orderedUnits); } @@ -145,7 +145,7 @@ private void persistVideoUnitWithLecture() { this.videoUnit = videoUnitRepository.save(this.videoUnit); lecture1.addLectureUnit(this.videoUnit); lecture1 = lectureRepository.save(lecture1); - this.videoUnit = (VideoUnit) lectureRepository.findByIdWithLectureUnits(lecture1.getId()).orElseThrow().getLectureUnits().stream().findFirst().orElseThrow(); + this.videoUnit = (VideoUnit) lectureRepository.findByIdWithLectureUnitsAndAttachments(lecture1.getId()).orElseThrow().getLectureUnits().stream().findFirst().orElseThrow(); } @Test @@ -153,7 +153,7 @@ private void persistVideoUnitWithLecture() { void updateVideoUnit_InstructorNotInCourse_shouldReturnForbidden() throws Exception { persistVideoUnitWithLecture(); - this.videoUnit = (VideoUnit) lectureRepository.findByIdWithLectureUnits(lecture1.getId()).orElseThrow().getLectureUnits().stream().findFirst().orElseThrow(); + this.videoUnit = (VideoUnit) lectureRepository.findByIdWithLectureUnitsAndAttachments(lecture1.getId()).orElseThrow().getLectureUnits().stream().findFirst().orElseThrow(); this.videoUnit.setDescription("Changed"); this.videoUnit.setSource("https://www.youtube.com/embed/8iU8LPEa4o0"); this.videoUnit = request.putWithResponseBody("/api/lectures/" + lecture1.getId() + "/video-units", this.videoUnit, VideoUnit.class, HttpStatus.FORBIDDEN); @@ -164,7 +164,7 @@ void updateVideoUnit_InstructorNotInCourse_shouldReturnForbidden() throws Except void updateVideoUnit_noId_shouldReturnBadRequest() throws Exception { persistVideoUnitWithLecture(); - this.videoUnit = (VideoUnit) lectureRepository.findByIdWithLectureUnits(lecture1.getId()).orElseThrow().getLectureUnits().stream().findFirst().orElseThrow(); + this.videoUnit = (VideoUnit) lectureRepository.findByIdWithLectureUnitsAndAttachments(lecture1.getId()).orElseThrow().getLectureUnits().stream().findFirst().orElseThrow(); this.videoUnit.setId(null); this.videoUnit = request.putWithResponseBody("/api/lectures/" + lecture1.getId() + "/video-units", this.videoUnit, VideoUnit.class, HttpStatus.BAD_REQUEST); } @@ -174,7 +174,7 @@ void updateVideoUnit_noId_shouldReturnBadRequest() throws Exception { void getVideoUnit_correctId_shouldReturnVideoUnit() throws Exception { persistVideoUnitWithLecture(); - this.videoUnit = (VideoUnit) lectureRepository.findByIdWithLectureUnits(lecture1.getId()).orElseThrow().getLectureUnits().stream().findFirst().orElseThrow(); + this.videoUnit = (VideoUnit) lectureRepository.findByIdWithLectureUnitsAndAttachments(lecture1.getId()).orElseThrow().getLectureUnits().stream().findFirst().orElseThrow(); VideoUnit videoUnitFromRequest = request.get("/api/lectures/" + lecture1.getId() + "/video-units/" + this.videoUnit.getId(), HttpStatus.OK, VideoUnit.class); assertThat(this.videoUnit.getId()).isEqualTo(videoUnitFromRequest.getId()); } @@ -184,7 +184,7 @@ void getVideoUnit_correctId_shouldReturnVideoUnit() throws Exception { void deleteVideoUnit_correctId_shouldDeleteVideoUnit() throws Exception { persistVideoUnitWithLecture(); - this.videoUnit = (VideoUnit) lectureRepository.findByIdWithLectureUnits(lecture1.getId()).orElseThrow().getLectureUnits().stream().findFirst().orElseThrow(); + this.videoUnit = (VideoUnit) lectureRepository.findByIdWithLectureUnitsAndAttachments(lecture1.getId()).orElseThrow().getLectureUnits().stream().findFirst().orElseThrow(); assertThat(this.videoUnit.getId()).isNotNull(); request.delete("/api/lectures/" + lecture1.getId() + "/lecture-units/" + this.videoUnit.getId(), HttpStatus.OK); request.get("/api/lectures/" + lecture1.getId() + "/video-units/" + this.videoUnit.getId(), HttpStatus.NOT_FOUND, VideoUnit.class); diff --git a/src/test/java/de/tum/in/www1/artemis/service/LectureImportServiceTest.java b/src/test/java/de/tum/in/www1/artemis/service/LectureImportServiceTest.java index 264fa1646105..c7ebe13c567f 100644 --- a/src/test/java/de/tum/in/www1/artemis/service/LectureImportServiceTest.java +++ b/src/test/java/de/tum/in/www1/artemis/service/LectureImportServiceTest.java @@ -54,7 +54,7 @@ void initTestCase() throws Exception { List courses = lectureUtilService.createCoursesWithExercisesAndLecturesAndLectureUnits(TEST_PREFIX, false, true, 0); Course course1 = this.courseRepository.findByIdWithExercisesAndLecturesElseThrow(courses.get(0).getId()); long lecture1Id = course1.getLectures().stream().findFirst().orElseThrow().getId(); - this.lecture1 = this.lectureRepository.findByIdWithLectureUnitsAndCompetenciesElseThrow(lecture1Id); + this.lecture1 = this.lectureRepository.findByIdWithAttachmentsAndPostsAndLectureUnitsAndCompetenciesAndCompletionsElseThrow(lecture1Id); this.course2 = courseUtilService.createCourse(); assertThat(this.lecture1.getLectureUnits()).isNotEmpty(); @@ -78,7 +78,7 @@ void testImportLectureToCourse() { // Find the imported lecture and fetch it with lecture units Long lecture2Id = this.course2.getLectures().stream().skip(lectureCount).findFirst().orElseThrow().getId(); - Lecture lecture2 = this.lectureRepository.findByIdWithLectureUnitsAndCompetenciesElseThrow(lecture2Id); + Lecture lecture2 = this.lectureRepository.findByIdWithAttachmentsAndPostsAndLectureUnitsAndCompetenciesAndCompletionsElseThrow(lecture2Id); assertThat(lecture2.getTitle()).isEqualTo(this.lecture1.getTitle()); assertThat(lecture2.getDescription()).isNotNull().isEqualTo(this.lecture1.getDescription()); From 7d294a3e3dab35bf65fdda69b7e3fe164c8f24ba Mon Sep 17 00:00:00 2001 From: Julian Christl Date: Mon, 8 Jan 2024 08:09:42 +0100 Subject: [PATCH 24/25] Development: Remove unused endpoint to get single complaint (#7852) --- .../web/rest/ComplaintResponseResource.java | 81 +------------------ .../complaints/complaint-response.service.ts | 6 -- .../AssessmentComplaintIntegrationTest.java | 74 ----------------- ...ssessmentTeamComplaintIntegrationTest.java | 13 --- .../complaint-response.service.spec.ts | 11 --- 5 files changed, 1 insertion(+), 184 deletions(-) diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/ComplaintResponseResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/ComplaintResponseResource.java index 3e052bd6716c..36b9bca73f1d 100644 --- a/src/main/java/de/tum/in/www1/artemis/web/rest/ComplaintResponseResource.java +++ b/src/main/java/de/tum/in/www1/artemis/web/rest/ComplaintResponseResource.java @@ -1,7 +1,5 @@ package de.tum.in.www1.artemis.web.rest; -import java.security.Principal; -import java.util.Objects; import java.util.Optional; import org.slf4j.Logger; @@ -11,18 +9,11 @@ import org.springframework.web.bind.annotation.*; import de.tum.in.www1.artemis.domain.*; -import de.tum.in.www1.artemis.domain.participation.Participant; -import de.tum.in.www1.artemis.domain.participation.StudentParticipation; import de.tum.in.www1.artemis.repository.ComplaintRepository; -import de.tum.in.www1.artemis.repository.ComplaintResponseRepository; import de.tum.in.www1.artemis.repository.UserRepository; -import de.tum.in.www1.artemis.security.annotations.EnforceAtLeastStudent; import de.tum.in.www1.artemis.security.annotations.EnforceAtLeastTutor; -import de.tum.in.www1.artemis.service.AuthorizationCheckService; import de.tum.in.www1.artemis.service.ComplaintResponseService; import de.tum.in.www1.artemis.web.rest.errors.AccessForbiddenException; -import de.tum.in.www1.artemis.web.rest.errors.EntityNotFoundException; -import tech.jhipster.web.util.ResponseUtil; /** * REST controller for managing complaints. @@ -35,22 +26,15 @@ public class ComplaintResponseResource { public static final String ENTITY_NAME = "complaintResponse"; - private final ComplaintResponseRepository complaintResponseRepository; - private final ComplaintRepository complaintRepository; private final ComplaintResponseService complaintResponseService; - private final AuthorizationCheckService authorizationCheckService; - private final UserRepository userRepository; - public ComplaintResponseResource(ComplaintResponseRepository complaintResponseRepository, ComplaintResponseService complaintResponseService, - AuthorizationCheckService authorizationCheckService, UserRepository userRepository, ComplaintRepository complaintRepository) { - this.complaintResponseRepository = complaintResponseRepository; + public ComplaintResponseResource(ComplaintResponseService complaintResponseService, UserRepository userRepository, ComplaintRepository complaintRepository) { this.complaintResponseService = complaintResponseService; this.complaintRepository = complaintRepository; - this.authorizationCheckService = authorizationCheckService; this.userRepository = userRepository; } @@ -121,69 +105,6 @@ public ResponseEntity resolveComplaint(@RequestBody Complaint return ResponseEntity.ok().body(updatedComplaintResponse); } - /** - * Get /complaint-responses/complaint/:id get a complaint response associated with the complaint "id" - * - * @param complaintId the id of the complaint for which we want to find a linked response - * @param principal the user who called the method - * @return the ResponseEntity with status 200 (OK) and with body the complaint response, or with status 404 (Not Found) - */ - // TODO: change URL to /complaint-responses?complaintId={complaintId} - @GetMapping("/complaint-responses/complaint/{complaintId}") - @EnforceAtLeastStudent - public ResponseEntity getComplaintResponseByComplaintId(@PathVariable long complaintId, Principal principal) { - log.debug("REST request to get ComplaintResponse associated to complaint : {}", complaintId); - Optional complaintResponse = complaintResponseRepository.findByComplaint_Id(complaintId); - return handleComplaintResponse(complaintId, principal, complaintResponse); - } - - private ResponseEntity handleComplaintResponse(long complaintId, Principal principal, Optional optionalComplaintResponse) { - if (optionalComplaintResponse.isEmpty()) { - throw new EntityNotFoundException("ComplaintResponse with " + complaintId + " was not found!"); - } - var user = userRepository.getUserWithGroupsAndAuthorities(); - var complaintResponse = optionalComplaintResponse.get(); - // All tutors and higher can see this, and also the students who first open the complaint - Participant originalAuthor = complaintResponse.getComplaint().getParticipant(); - StudentParticipation studentParticipation = (StudentParticipation) complaintResponse.getComplaint().getResult().getParticipation(); - Exercise exercise = studentParticipation.getExercise(); - var atLeastTA = authorizationCheckService.isAtLeastTeachingAssistantForExercise(exercise, user); - if (!atLeastTA && !isOriginalAuthor(principal, originalAuthor)) { - throw new AccessForbiddenException("Insufficient permission for this complaint response"); - } - - if (!authorizationCheckService.isAtLeastInstructorForExercise(exercise, user)) { - complaintResponse.getComplaint().setParticipant(null); - } - - if (!atLeastTA) { - complaintResponse.setReviewer(null); - } - - if (isOriginalAuthor(principal, originalAuthor)) { - // hide complaint completely if the user is the student who created the complaint - complaintResponse.setComplaint(null); - } - else { - // hide unnecessary information - complaintResponse.getComplaint().getResult().setParticipation(null); - complaintResponse.getComplaint().getResult().setSubmission(null); - } - return ResponseUtil.wrapOrNotFound(optionalComplaintResponse); - } - - private boolean isOriginalAuthor(Principal principal, Participant originalAuthor) { - if (originalAuthor instanceof User) { - return Objects.equals(((User) originalAuthor).getLogin(), principal.getName()); - } - else if (originalAuthor instanceof Team) { - return ((Team) originalAuthor).hasStudentWithLogin(principal.getName()); - } - else { - throw new Error("Unknown Participant type"); - } - } - private Complaint getComplaintFromDatabaseAndCheckAccessRights(long complaintId) { Optional complaintFromDatabaseOptional = complaintRepository.findByIdWithEagerAssessor(complaintId); if (complaintFromDatabaseOptional.isEmpty()) { diff --git a/src/main/webapp/app/complaints/complaint-response.service.ts b/src/main/webapp/app/complaints/complaint-response.service.ts index 1617793ac53b..e924737bd3ac 100644 --- a/src/main/webapp/app/complaints/complaint-response.service.ts +++ b/src/main/webapp/app/complaints/complaint-response.service.ts @@ -73,12 +73,6 @@ export class ComplaintResponseService { .pipe(map((res: EntityResponseType) => this.convertComplaintResponseEntityResponseDatesFromServer(res))); } - findByComplaintId(complaintId: number): Observable { - return this.http - .get(`${this.resourceUrl}/complaint/${complaintId}`, { observe: 'response' }) - .pipe(map((res: EntityResponseType) => this.convertComplaintResponseEntityResponseDatesFromServer(res))); - } - public convertComplaintResponseDatesFromClient(complaintResponse: ComplaintResponse): ComplaintResponse { return Object.assign({}, complaintResponse, { submittedTime: convertDateFromClient(complaintResponse.submittedTime), diff --git a/src/test/java/de/tum/in/www1/artemis/assessment/AssessmentComplaintIntegrationTest.java b/src/test/java/de/tum/in/www1/artemis/assessment/AssessmentComplaintIntegrationTest.java index 1e9595153988..3200f3d464c4 100644 --- a/src/test/java/de/tum/in/www1/artemis/assessment/AssessmentComplaintIntegrationTest.java +++ b/src/test/java/de/tum/in/www1/artemis/assessment/AssessmentComplaintIntegrationTest.java @@ -580,80 +580,6 @@ void getComplaintsForAssessmentDashboard_testRun_emptyComplaints() throws Except assertThat(complaints).hasSize(0); } - @Test - @WithMockUser(username = TEST_PREFIX + "student1") - void getComplaintResponseByComplaintId_reviewerHiddenForStudent() throws Exception { - complaint.setParticipant(userUtilService.getUserByLogin(TEST_PREFIX + "student1")); - complaintRepo.save(complaint); - - ComplaintResponse complaintResponse = new ComplaintResponse().complaint(complaint.accepted(false)).responseText("rejected") - .reviewer(userUtilService.getUserByLogin(TEST_PREFIX + "tutor1")); - complaintResponseRepo.save(complaintResponse); - - ComplaintResponse receivedComplaintResponse = request.get("/api/complaint-responses/complaint/" + complaint.getId(), HttpStatus.OK, ComplaintResponse.class); - - assertThat(receivedComplaintResponse.getReviewer()).as("reviewer is not set").isNull(); - assertThat(receivedComplaintResponse.getComplaint()).as("complaint is not set").isNull(); - } - - @Test - @WithMockUser(username = TEST_PREFIX + "tutor1") - void getComplaintResponseByComplaintId_sensitiveDataHiddenForTutor() throws Exception { - complaint.setParticipant(userUtilService.getUserByLogin(TEST_PREFIX + "student1")); - - complaint = complaintRepo.save(complaint); - ComplaintResponse complaintResponse = new ComplaintResponse(); - complaintResponse.setComplaint(complaint); - complaintResponse.getComplaint().setAccepted(false); - complaintResponse.setResponseText("rejected"); - complaintResponse = complaintResponseRepo.save(complaintResponse); - complaintResponse.setReviewer(userUtilService.getUserByLogin(TEST_PREFIX + "tutor1")); - - complaintResponseRepo.save(complaintResponse); - - ComplaintResponse receivedComplaintResponse = request.get("/api/complaint-responses/complaint/" + complaint.getId(), HttpStatus.OK, ComplaintResponse.class); - - Complaint receivedComplaint = receivedComplaintResponse.getComplaint(); - assertThat(receivedComplaint.getParticipant()).as("student is not set").isNull(); - assertThat(receivedComplaint.getResult().getParticipation()).as("participation is not set").isNull(); - assertThat(receivedComplaint.getResult().getSubmission()).as("submission is not set").isNull(); - } - - @Test - @WithMockUser(username = TEST_PREFIX + "instructor1") - void getComplaintResponseByComplaintId_sensitiveDataHiddenForInstructor() throws Exception { - complaint.setParticipant(userUtilService.getUserByLogin(TEST_PREFIX + "student1")); - complaint = complaintRepo.save(complaint); - ComplaintResponse complaintResponse = new ComplaintResponse(); - complaintResponse.setComplaint(complaint); - complaintResponse.getComplaint().setAccepted(false); - complaintResponse.setResponseText("rejected"); - complaintResponse = complaintResponseRepo.save(complaintResponse); - complaintResponse.setReviewer(userUtilService.getUserByLogin(TEST_PREFIX + "instructor1")); - - complaintResponseRepo.save(complaintResponse); - - ComplaintResponse receivedComplaintResponse = request.get("/api/complaint-responses/complaint/" + complaint.getId(), HttpStatus.OK, ComplaintResponse.class); - - Complaint receivedComplaint = receivedComplaintResponse.getComplaint(); - assertThat(receivedComplaint.getParticipant()).as("student is set").isNotNull(); - assertThat(receivedComplaint.getResult().getParticipation()).as("participation is not set").isNull(); - assertThat(receivedComplaint.getResult().getSubmission()).as("submission is not set").isNull(); - } - - @Test - @WithMockUser(username = TEST_PREFIX + "student2") - void getComplaintResponseByComplaintId_studentNotOriginalAuthor_forbidden() throws Exception { - complaint.setParticipant(userUtilService.getUserByLogin(TEST_PREFIX + "student1")); - complaintRepo.save(complaint); - - ComplaintResponse complaintResponse = new ComplaintResponse().complaint(complaint.accepted(false)).responseText("rejected") - .reviewer(userUtilService.getUserByLogin(TEST_PREFIX + "tutor1")); - complaintResponseRepo.save(complaintResponse); - - request.get("/api/complaint-responses/complaint/" + complaint.getId(), HttpStatus.FORBIDDEN, ComplaintResponse.class); - } - @Test @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") void getSubmittedComplaints_byComplaintType() throws Exception { diff --git a/src/test/java/de/tum/in/www1/artemis/assessment/AssessmentTeamComplaintIntegrationTest.java b/src/test/java/de/tum/in/www1/artemis/assessment/AssessmentTeamComplaintIntegrationTest.java index 4a482f1c05ac..c65f46082b38 100644 --- a/src/test/java/de/tum/in/www1/artemis/assessment/AssessmentTeamComplaintIntegrationTest.java +++ b/src/test/java/de/tum/in/www1/artemis/assessment/AssessmentTeamComplaintIntegrationTest.java @@ -261,19 +261,6 @@ void getComplaintByResultId_studentAndNotPartOfTeam_forbidden() throws Exception request.get("/api/complaints/submissions/" + modelingSubmission.getId(), HttpStatus.FORBIDDEN, Complaint.class); } - @Test - @WithMockUser(username = TEST_PREFIX + "student1") - void getComplaintResponseByComplaintId_studentNotPartOfTeam_forbidden() throws Exception { - complaint.setParticipant(team); - complaintRepo.save(complaint); - - ComplaintResponse complaintResponse = new ComplaintResponse().complaint(complaint.accepted(false)).responseText("rejected") - .reviewer(userUtilService.getUserByLogin(TEST_PREFIX + "tutor1")); - complaintResponseRepo.save(complaintResponse); - - request.get("/api/complaint-responses/complaint/" + complaint.getId(), HttpStatus.FORBIDDEN, ComplaintResponse.class); - } - private void saveModelingSubmissionAndAssessment() throws Exception { modelingSubmission = ParticipationFactory.generateModelingSubmission(FileUtils.loadFileFromResources("test-data/model-submission/model.54727.json"), true); modelingSubmission = modelingExerciseUtilService.addModelingTeamSubmission(modelingExercise, modelingSubmission, team); diff --git a/src/test/javascript/spec/component/complaints/complaint-response.service.spec.ts b/src/test/javascript/spec/component/complaints/complaint-response.service.spec.ts index a63517d5a671..ecc547212587 100644 --- a/src/test/javascript/spec/component/complaints/complaint-response.service.spec.ts +++ b/src/test/javascript/spec/component/complaints/complaint-response.service.spec.ts @@ -117,15 +117,4 @@ describe('ComplaintResponseService', () => { req.flush(returnedFromService); expect(expectedComplaintResponse.body).toEqual(defaultComplaintResponse); }); - - it('should call findByComplaintId', async () => { - const returnedFromService = { ...defaultComplaintResponse }; - complaintResponseService - .findByComplaintId(1) - .pipe(take(1)) - .subscribe((resp) => (expectedComplaintResponse = resp)); - const req = httpTestingController.expectOne({ method: 'GET' }); - req.flush(returnedFromService); - expect(expectedComplaintResponse.body).toEqual(defaultComplaintResponse); - }); }); From 2ea0ab519f81087967d1c46b955add3cfeb7b4ef Mon Sep 17 00:00:00 2001 From: Rizky Riyaldhi Date: Mon, 8 Jan 2024 14:30:27 +0100 Subject: [PATCH 25/25] Exam mode: Use quiz pool in student exam generation (#7583) --- .../www1/artemis/domain/exam/StudentExam.java | 24 +++ .../in/www1/artemis/domain/quiz/QuizPool.java | 2 +- .../repository/StudentExamRepository.java | 58 +++--- .../www1/artemis/service/QuizPoolService.java | 52 ++++- .../exam/ExamQuizQuestionsGenerator.java | 19 ++ .../service/exam/StudentExamService.java | 50 ++++- .../www1/artemis/web/rest/ExamResource.java | 10 +- .../artemis/web/rest/QuizPoolResource.java | 2 +- .../changelog/20231211112800_changelog.xml | 21 ++ .../resources/config/liquibase/master.xml | 1 + .../artemis/exam/ExamIntegrationTest.java | 182 ++++++++++++++---- .../service/exam/ExamQuizServiceTest.java | 8 +- 12 files changed, 357 insertions(+), 72 deletions(-) create mode 100644 src/main/java/de/tum/in/www1/artemis/service/exam/ExamQuizQuestionsGenerator.java create mode 100644 src/main/resources/config/liquibase/changelog/20231211112800_changelog.xml diff --git a/src/main/java/de/tum/in/www1/artemis/domain/exam/StudentExam.java b/src/main/java/de/tum/in/www1/artemis/domain/exam/StudentExam.java index 5bff813357fe..ac9fa5c92faa 100644 --- a/src/main/java/de/tum/in/www1/artemis/domain/exam/StudentExam.java +++ b/src/main/java/de/tum/in/www1/artemis/domain/exam/StudentExam.java @@ -5,6 +5,7 @@ import javax.persistence.*; +import org.hibernate.Hibernate; import org.hibernate.annotations.Cache; import org.hibernate.annotations.CacheConcurrencyStrategy; @@ -15,6 +16,7 @@ import de.tum.in.www1.artemis.domain.AbstractAuditingEntity; import de.tum.in.www1.artemis.domain.Exercise; import de.tum.in.www1.artemis.domain.User; +import de.tum.in.www1.artemis.domain.quiz.QuizQuestion; @Entity @Table(name = "student_exam") @@ -63,6 +65,10 @@ public class StudentExam extends AbstractAuditingEntity { @JsonIgnoreProperties("studentExam") private Set examSessions = new HashSet<>(); + @ManyToMany + @JoinTable(name = "student_exam_quiz_question", joinColumns = @JoinColumn(name = "student_exam_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "quiz_question_id", referencedColumnName = "id")) + private List quizQuestions = new ArrayList<>(); + public Boolean isSubmitted() { return submitted; } @@ -160,6 +166,24 @@ public void setExamSessions(Set examSessions) { this.examSessions = examSessions; } + /** + * Returns a list of quiz questions associated with the student exam. + * If the quizQuestions list is not null and has been initialized, it returns the list of quiz questions. + * Otherwise, it returns an empty list. + * + * @return the list of quiz questions associated with the student exam + */ + public List getQuizQuestions() { + if (quizQuestions != null && Hibernate.isInitialized(quizQuestions)) { + return quizQuestions; + } + return Collections.emptyList(); + } + + public void setQuizQuestions(List quizQuestions) { + this.quizQuestions = quizQuestions; + } + /** * Adds the given exam session to the student exam * diff --git a/src/main/java/de/tum/in/www1/artemis/domain/quiz/QuizPool.java b/src/main/java/de/tum/in/www1/artemis/domain/quiz/QuizPool.java index d051a5904d99..5b9d2e0af5bb 100644 --- a/src/main/java/de/tum/in/www1/artemis/domain/quiz/QuizPool.java +++ b/src/main/java/de/tum/in/www1/artemis/domain/quiz/QuizPool.java @@ -74,7 +74,7 @@ public void setRandomizeQuestionOrder(Boolean randomizeQuestionOrder) { @Override public void setQuestionParent(QuizQuestion quizQuestion) { - // Do nothing since the relationship between QuizPool and QuizQuestion is defined in QuizPool. + // Do nothing since the relationship between QuizPool and QuizQuestion is already defined above. } @Override diff --git a/src/main/java/de/tum/in/www1/artemis/repository/StudentExamRepository.java b/src/main/java/de/tum/in/www1/artemis/repository/StudentExamRepository.java index fcfd65dfbd99..9c190dce60d5 100644 --- a/src/main/java/de/tum/in/www1/artemis/repository/StudentExamRepository.java +++ b/src/main/java/de/tum/in/www1/artemis/repository/StudentExamRepository.java @@ -20,7 +20,9 @@ import de.tum.in.www1.artemis.domain.exam.ExerciseGroup; import de.tum.in.www1.artemis.domain.exam.StudentExam; import de.tum.in.www1.artemis.domain.participation.StudentParticipation; +import de.tum.in.www1.artemis.domain.quiz.QuizQuestion; import de.tum.in.www1.artemis.service.ExerciseDateService; +import de.tum.in.www1.artemis.service.exam.ExamQuizQuestionsGenerator; import de.tum.in.www1.artemis.web.rest.errors.EntityNotFoundException; /** @@ -376,11 +378,12 @@ default Integer findMaxWorkingTimeByExamIdElseThrow(Long examId) { /** * Generates random exams for each user in the given users set and saves them. * - * @param exam exam for which the individual student exams will be generated - * @param users users for which the individual exams will be generated + * @param exam exam for which the individual student exams will be generated + * @param users users for which the individual exams will be generated + * @param examQuizQuestionsGenerator generator to generate quiz questions for the exam * @return List of StudentExams generated for the given users */ - default List createRandomStudentExams(Exam exam, Set users) { + default List createRandomStudentExams(Exam exam, Set users, ExamQuizQuestionsGenerator examQuizQuestionsGenerator) { List studentExams = new ArrayList<>(); SecureRandom random = new SecureRandom(); long numberOfOptionalExercises = exam.getNumberOfExercisesInExam() - exam.getExerciseGroups().stream().filter(ExerciseGroup::getIsMandatory).count(); @@ -421,6 +424,8 @@ default List createRandomStudentExams(Exam exam, Set users) { if (Boolean.TRUE.equals(exam.getRandomizeExerciseOrder())) { Collections.shuffle(studentExam.getExercises()); } + List quizQuestions = examQuizQuestionsGenerator.generateQuizQuestionsForExam(exam.getId()); + studentExam.setQuizQuestions(quizQuestions); studentExams.add(studentExam); } @@ -453,10 +458,11 @@ private Exercise selectRandomExercise(SecureRandom random, ExerciseGroup exercis * Generates the student exams randomly based on the exam configuration and the exercise groups * Important: the passed exams needs to include the registered users, exercise groups and exercises (eagerly loaded) * - * @param exam with eagerly loaded registered users, exerciseGroups and exercises loaded + * @param exam with eagerly loaded registered users, exerciseGroups and exercises loaded + * @param examQuizQuestionsGenerator generator to generate quiz questions for the exam * @return the list of student exams with their corresponding users */ - default List generateStudentExams(final Exam exam) { + default List generateStudentExams(final Exam exam, ExamQuizQuestionsGenerator examQuizQuestionsGenerator) { final var existingStudentExams = findByExamId(exam.getId()); // https://jira.spring.io/browse/DATAJPA-1367 deleteInBatch does not work, because it does not cascade the deletion of existing exam sessions, therefore use deleteAll deleteAll(existingStudentExams); @@ -464,28 +470,34 @@ default List generateStudentExams(final Exam exam) { Set users = exam.getRegisteredUsers(); // StudentExams are saved in the called method - return createRandomStudentExams(exam, users); + return createRandomStudentExams(exam, users, examQuizQuestionsGenerator); } /** - * Generates the missing student exams randomly based on the exam configuration and the exercise groups. - * The difference between all registered users and the users who already have an individual exam is the set of users for which student exams will be created. - *

- * Important: the passed exams needs to include the registered users, exercise groups and exercises (eagerly loaded) + * Get all student exams for the given exam id with quiz questions. * - * @param exam with eagerly loaded registered users, exerciseGroups and exercises loaded - * @return the list of student exams with their corresponding users + * @param ids the ids of the student exams + * @return the list of student exams with quiz questions */ - default List generateMissingStudentExams(Exam exam) { - - // Get all users who already have an individual exam - Set usersWithStudentExam = findUsersWithStudentExamsForExam(exam.getId()); - - // Get all students who don't have an exam yet - Set missingUsers = exam.getRegisteredUsers(); - missingUsers.removeAll(usersWithStudentExam); + @Query(""" + SELECT DISTINCT se + FROM StudentExam se + LEFT JOIN FETCH se.quizQuestions qq + WHERE se.id IN :ids + """) + List findAllWithEagerQuizQuestionsById(List ids); - // StudentExams are saved in the called method - return createRandomStudentExams(exam, missingUsers); - } + /** + * Get all student exams for the given exam id with exercises. + * + * @param ids the ids of the student exams + * @return the list of student exams with exercises + */ + @Query(""" + SELECT DISTINCT se + FROM StudentExam se + LEFT JOIN FETCH se.exercises e + WHERE se.id IN :ids + """) + List findAllWithEagerExercisesById(List ids); } diff --git a/src/main/java/de/tum/in/www1/artemis/service/QuizPoolService.java b/src/main/java/de/tum/in/www1/artemis/service/QuizPoolService.java index 5ac3829a86c6..7633ee182809 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/QuizPoolService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/QuizPoolService.java @@ -1,5 +1,8 @@ package de.tum.in.www1.artemis.service; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -21,6 +24,7 @@ import de.tum.in.www1.artemis.repository.QuizGroupRepository; import de.tum.in.www1.artemis.repository.QuizPoolRepository; import de.tum.in.www1.artemis.repository.ShortAnswerMappingRepository; +import de.tum.in.www1.artemis.service.exam.ExamQuizQuestionsGenerator; import de.tum.in.www1.artemis.web.rest.errors.BadRequestAlertException; import de.tum.in.www1.artemis.web.rest.errors.EntityNotFoundException; @@ -28,7 +32,7 @@ * This service contains the functions to manage QuizPool entity. */ @Service -public class QuizPoolService extends QuizService { +public class QuizPoolService extends QuizService implements ExamQuizQuestionsGenerator { private static final String ENTITY_NAME = "quizPool"; @@ -97,7 +101,7 @@ public QuizPool update(Long examId, QuizPool quizPool) { * @param examId the id of the exam to be searched * @return optional quiz pool that belongs to the given exam id */ - public Optional findWithQuizQuestionsByExamId(Long examId) { + public Optional findWithQuizGroupsAndQuestionsByExamId(Long examId) { Optional quizPoolOptional = quizPoolRepository.findWithEagerQuizQuestionsByExamId(examId); if (quizPoolOptional.isPresent()) { QuizPool quizPool = quizPoolOptional.get(); @@ -150,4 +154,48 @@ private void removeUnusedQuizGroup(List existingQuizGroupIds, List generateQuizQuestionsForExam(long examId) { + Optional quizPoolOptional = findWithQuizGroupsAndQuestionsByExamId(examId); + if (quizPoolOptional.isPresent()) { + QuizPool quizPool = quizPoolOptional.get(); + List quizGroups = quizPool.getQuizGroups(); + List quizQuestions = quizPool.getQuizQuestions(); + + Map> quizGroupQuestionsMap = getQuizQuestionsGroup(quizGroups, quizQuestions); + return generateQuizQuestions(quizGroupQuestionsMap, quizGroups, quizQuestions); + } + else { + return new ArrayList<>(); + } + } + + private static Map> getQuizQuestionsGroup(List quizGroups, List quizQuestions) { + Map> quizGroupQuestionsMap = new HashMap<>(); + for (QuizGroup quizGroup : quizGroups) { + quizGroupQuestionsMap.put(quizGroup.getId(), new ArrayList<>()); + } + for (QuizQuestion quizQuestion : quizQuestions) { + if (quizQuestion.getQuizGroup() != null) { + quizGroupQuestionsMap.get(quizQuestion.getQuizGroup().getId()).add(quizQuestion); + } + } + return quizGroupQuestionsMap; + } + + private static List generateQuizQuestions(Map> quizGroupQuestionsMap, List quizGroups, List quizQuestions) { + SecureRandom random = new SecureRandom(); + List results = new ArrayList<>(); + for (QuizGroup quizGroup : quizGroups) { + List quizGroupQuestions = quizGroupQuestionsMap.get(quizGroup.getId()); + results.add(quizGroupQuestions.get(random.nextInt(quizGroupQuestions.size()))); + } + for (QuizQuestion quizQuestion : quizQuestions) { + if (quizQuestion.getQuizGroup() == null) { + results.add(quizQuestion); + } + } + return results; + } } diff --git a/src/main/java/de/tum/in/www1/artemis/service/exam/ExamQuizQuestionsGenerator.java b/src/main/java/de/tum/in/www1/artemis/service/exam/ExamQuizQuestionsGenerator.java new file mode 100644 index 000000000000..6aa4ae36060b --- /dev/null +++ b/src/main/java/de/tum/in/www1/artemis/service/exam/ExamQuizQuestionsGenerator.java @@ -0,0 +1,19 @@ +package de.tum.in.www1.artemis.service.exam; + +import java.util.List; + +import de.tum.in.www1.artemis.domain.quiz.QuizQuestion; + +/** + * Service Interface for generating quiz questions for an exam + */ +public interface ExamQuizQuestionsGenerator { + + /** + * Generates quiz questions for an exam + * + * @param examId the id of the exam for which quiz questions should be generated + * @return the list of generated quiz questions + */ + List generateQuizQuestionsForExam(long examId); +} diff --git a/src/main/java/de/tum/in/www1/artemis/service/exam/StudentExamService.java b/src/main/java/de/tum/in/www1/artemis/service/exam/StudentExamService.java index 3b7bc810cbfd..c09945996c8f 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/exam/StudentExamService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/exam/StudentExamService.java @@ -22,6 +22,7 @@ import org.springframework.cache.CacheManager; import org.springframework.scheduling.TaskScheduler; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import de.tum.in.www1.artemis.domain.*; import de.tum.in.www1.artemis.domain.enumeration.InitializationState; @@ -89,13 +90,15 @@ public class StudentExamService { private final TaskScheduler scheduler; + private final ExamQuizQuestionsGenerator examQuizQuestionsGenerator; + public StudentExamService(StudentExamRepository studentExamRepository, UserRepository userRepository, ParticipationService participationService, QuizSubmissionRepository quizSubmissionRepository, SubmittedAnswerRepository submittedAnswerRepository, TextSubmissionRepository textSubmissionRepository, ModelingSubmissionRepository modelingSubmissionRepository, SubmissionVersionService submissionVersionService, ProgrammingExerciseParticipationService programmingExerciseParticipationService, SubmissionService submissionService, StudentParticipationRepository studentParticipationRepository, ExamQuizService examQuizService, ProgrammingExerciseRepository programmingExerciseRepository, ProgrammingTriggerService programmingTriggerService, ExamRepository examRepository, CacheManager cacheManager, WebsocketMessagingService websocketMessagingService, - @Qualifier("taskScheduler") TaskScheduler scheduler) { + @Qualifier("taskScheduler") TaskScheduler scheduler, QuizPoolService quizPoolService) { this.participationService = participationService; this.studentExamRepository = studentExamRepository; this.userRepository = userRepository; @@ -114,6 +117,7 @@ public StudentExamService(StudentExamRepository studentExamRepository, UserRepos this.cacheManager = cacheManager; this.websocketMessagingService = websocketMessagingService; this.scheduler = scheduler; + this.examQuizQuestionsGenerator = quizPoolService; } /** @@ -782,6 +786,48 @@ private StudentExam generateIndividualStudentExam(Exam exam, User student) { // StudentExams are saved in the called method HashSet userHashSet = new HashSet<>(); userHashSet.add(student); - return studentExamRepository.createRandomStudentExams(exam, userHashSet).get(0); + return studentExamRepository.createRandomStudentExams(exam, userHashSet, examQuizQuestionsGenerator).get(0); + } + + /** + * Generates the student exams randomly based on the exam configuration and the exercise groups + * Important: the passed exams needs to include the registered users, exercise groups and exercises (eagerly loaded) + * + * @param exam with eagerly loaded registered users, exerciseGroups and exercises loaded + * @return the list of student exams with their corresponding users + */ + @Transactional + public List generateStudentExams(final Exam exam) { + final var existingStudentExams = studentExamRepository.findByExamId(exam.getId()); + // https://jira.spring.io/browse/DATAJPA-1367 deleteInBatch does not work, because it does not cascade the deletion of existing exam sessions, therefore use deleteAll + studentExamRepository.deleteAll(existingStudentExams); + + Set users = exam.getRegisteredUsers(); + + // StudentExams are saved in the called method + return studentExamRepository.createRandomStudentExams(exam, users, examQuizQuestionsGenerator); + } + + /** + * Generates the missing student exams randomly based on the exam configuration and the exercise groups. + * The difference between all registered users and the users who already have an individual exam is the set of users for which student exams will be created. + *

+ * Important: the passed exams needs to include the registered users, exercise groups and exercises (eagerly loaded) + * + * @param exam with eagerly loaded registered users, exerciseGroups and exercises loaded + * @return the list of student exams with their corresponding users + */ + @Transactional + public List generateMissingStudentExams(Exam exam) { + + // Get all users who already have an individual exam + Set usersWithStudentExam = studentExamRepository.findUsersWithStudentExamsForExam(exam.getId()); + + // Get all students who don't have an exam yet + Set missingUsers = exam.getRegisteredUsers(); + missingUsers.removeAll(usersWithStudentExam); + + // StudentExams are saved in the called method + return studentExamRepository.createRandomStudentExams(exam, missingUsers, examQuizQuestionsGenerator); } } diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/ExamResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/ExamResource.java index 617fbf7dd10a..904346a7716b 100644 --- a/src/main/java/de/tum/in/www1/artemis/web/rest/ExamResource.java +++ b/src/main/java/de/tum/in/www1/artemis/web/rest/ExamResource.java @@ -115,12 +115,15 @@ public class ExamResource { private final ExamLiveEventsService examLiveEventsService; + private final StudentExamService studentExamService; + public ExamResource(ProfileService profileService, UserRepository userRepository, CourseRepository courseRepository, ExamService examService, ExamDeletionService examDeletionService, ExamAccessService examAccessService, InstanceMessageSendService instanceMessageSendService, ExamRepository examRepository, SubmissionService submissionService, AuthorizationCheckService authCheckService, ExamDateService examDateService, TutorParticipationRepository tutorParticipationRepository, AssessmentDashboardService assessmentDashboardService, ExamRegistrationService examRegistrationService, StudentExamRepository studentExamRepository, ExamImportService examImportService, CustomAuditEventRepository auditEventRepository, ChannelService channelService, - ChannelRepository channelRepository, ExerciseRepository exerciseRepository, ExamSessionService examSessionRepository, ExamLiveEventsService examLiveEventsService) { + ChannelRepository channelRepository, ExerciseRepository exerciseRepository, ExamSessionService examSessionRepository, ExamLiveEventsService examLiveEventsService, + StudentExamService studentExamService) { this.profileService = profileService; this.userRepository = userRepository; this.courseRepository = courseRepository; @@ -143,6 +146,7 @@ public ExamResource(ProfileService profileService, UserRepository userRepository this.exerciseRepository = exerciseRepository; this.examSessionService = examSessionRepository; this.examLiveEventsService = examLiveEventsService; + this.studentExamService = studentExamService; } /** @@ -867,7 +871,7 @@ public ResponseEntity> generateStudentExams(@PathVariable Long // Reset existing student exams & participations in case they already exist examDeletionService.deleteStudentExamsAndExistingParticipationsForExam(exam.getId()); - List studentExams = studentExamRepository.generateStudentExams(exam); + List studentExams = studentExamService.generateStudentExams(exam); // we need to break a cycle for the serialization breakCyclesForSerialization(studentExams); @@ -911,7 +915,7 @@ public ResponseEntity> generateMissingStudentExams(@PathVariab log.info("REST request to generate missing student exams for exam {}", examId); final var exam = checkAccessForStudentExamGenerationAndLogAuditEvent(courseId, examId, Constants.GENERATE_MISSING_STUDENT_EXAMS); - List studentExams = studentExamRepository.generateMissingStudentExams(exam); + List studentExams = studentExamService.generateMissingStudentExams(exam); // we need to break a cycle for the serialization breakCyclesForSerialization(studentExams); diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/QuizPoolResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/QuizPoolResource.java index 544f5da0b592..84481c51d464 100644 --- a/src/main/java/de/tum/in/www1/artemis/web/rest/QuizPoolResource.java +++ b/src/main/java/de/tum/in/www1/artemis/web/rest/QuizPoolResource.java @@ -82,7 +82,7 @@ public ResponseEntity getQuizPool(@PathVariable Long courseId, @PathVa log.info("REST request to get QuizPool given examId : {}", examId); validateCourseRole(courseId); - QuizPool quizPool = quizPoolService.findWithQuizQuestionsByExamId(examId).orElse(null); + QuizPool quizPool = quizPoolService.findWithQuizGroupsAndQuestionsByExamId(examId).orElse(null); return ResponseEntity.ok().body(quizPool); } diff --git a/src/main/resources/config/liquibase/changelog/20231211112800_changelog.xml b/src/main/resources/config/liquibase/changelog/20231211112800_changelog.xml new file mode 100644 index 000000000000..3cf081d7bd54 --- /dev/null +++ b/src/main/resources/config/liquibase/changelog/20231211112800_changelog.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + diff --git a/src/main/resources/config/liquibase/master.xml b/src/main/resources/config/liquibase/master.xml index 99142f54d90e..e025c6df88c3 100644 --- a/src/main/resources/config/liquibase/master.xml +++ b/src/main/resources/config/liquibase/master.xml @@ -74,6 +74,7 @@ + diff --git a/src/test/java/de/tum/in/www1/artemis/exam/ExamIntegrationTest.java b/src/test/java/de/tum/in/www1/artemis/exam/ExamIntegrationTest.java index 6f15d0aa8027..c3f55e1f7bcd 100644 --- a/src/test/java/de/tum/in/www1/artemis/exam/ExamIntegrationTest.java +++ b/src/test/java/de/tum/in/www1/artemis/exam/ExamIntegrationTest.java @@ -39,7 +39,9 @@ import de.tum.in.www1.artemis.domain.modeling.ModelingSubmission; import de.tum.in.www1.artemis.domain.participation.StudentParticipation; import de.tum.in.www1.artemis.domain.quiz.QuizExercise; +import de.tum.in.www1.artemis.domain.quiz.QuizGroup; import de.tum.in.www1.artemis.domain.quiz.QuizPool; +import de.tum.in.www1.artemis.domain.quiz.QuizQuestion; import de.tum.in.www1.artemis.exercise.ExerciseUtilService; import de.tum.in.www1.artemis.exercise.modelingexercise.ModelingExerciseUtilService; import de.tum.in.www1.artemis.exercise.quizexercise.QuizExerciseFactory; @@ -47,6 +49,7 @@ import de.tum.in.www1.artemis.exercise.textexercise.TextExerciseUtilService; import de.tum.in.www1.artemis.repository.*; import de.tum.in.www1.artemis.repository.metis.conversation.ChannelRepository; +import de.tum.in.www1.artemis.service.QuizPoolService; import de.tum.in.www1.artemis.service.dto.StudentDTO; import de.tum.in.www1.artemis.service.exam.ExamAccessService; import de.tum.in.www1.artemis.service.exam.ExamDateService; @@ -134,6 +137,9 @@ class ExamIntegrationTest extends AbstractSpringIntegrationBambooBitbucketJiraTe @Autowired private ExamUserRepository examUserRepository; + @Autowired + private QuizPoolService quizPoolService; + @Autowired private QuizPoolRepository quizPoolRepository; @@ -219,18 +225,152 @@ void testGetAllActiveExams() throws Exception { void testGenerateStudentExams() throws Exception { Exam exam = examUtilService.setupExamWithExerciseGroupsExercisesRegisteredStudents(TEST_PREFIX, course1, 2); - // invoke generate student exams + generateStudentExams(exam); + + verifyStudentsExamAndExercisesAndQuizQuestions(exam, 0); + } + + @Test + @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") + void testGenerateStudentExamsWithQuizPool() throws Exception { + Exam exam = examUtilService.setupExamWithExerciseGroupsExercisesRegisteredStudents(TEST_PREFIX, course1, 2); + setupQuizPoolWithQuestionsForExam(exam); + + generateStudentExams(exam); + + verifyStudentsExamAndExercisesAndQuizQuestions(exam, 4); + } + + @Test + @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") + void testGenerateStudentExamsWithEmptyQuizPool() throws Exception { + Exam exam = examUtilService.setupExamWithExerciseGroupsExercisesRegisteredStudents(TEST_PREFIX, course1, 2); + setupEmptyQuizPoolForExam(exam); + + generateStudentExams(exam); + + verifyStudentsExamAndExercisesAndQuizQuestions(exam, 0); + } + + @Test + @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") + void testGenerateMissingStudentExams() throws Exception { + Exam exam = examUtilService.setupExamWithExerciseGroupsExercisesRegisteredStudents(TEST_PREFIX, course1, 1); + + generateStudentExams(exam); + + registerNewStudentsToExam(exam, 1); + generateMissingStudentExams(exam, 1); + verifyStudentsExamAndExercisesAndQuizQuestions(exam, 0); + + generateMissingStudentExams(exam, 0); + verifyStudentsExamAndExercisesAndQuizQuestions(exam, 0); + } + + @Test + @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") + void testGenerateMissingStudentExamsWithQuizPool() throws Exception { + Exam exam = examUtilService.setupExamWithExerciseGroupsExercisesRegisteredStudents(TEST_PREFIX, course1, 1); + setupQuizPoolWithQuestionsForExam(exam); + + generateStudentExams(exam); + + registerNewStudentsToExam(exam, 1); + generateMissingStudentExams(exam, 1); + verifyStudentsExamAndExercisesAndQuizQuestions(exam, 4); + + generateMissingStudentExams(exam, 0); + verifyStudentsExamAndExercisesAndQuizQuestions(exam, 4); + } + + @Test + @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") + void testGenerateMissingStudentExamsWithEmptyQuizPool() throws Exception { + Exam exam = examUtilService.setupExamWithExerciseGroupsExercisesRegisteredStudents(TEST_PREFIX, course1, 1); + setupEmptyQuizPoolForExam(exam); + + generateStudentExams(exam); + + registerNewStudentsToExam(exam, 1); + generateMissingStudentExams(exam, 1); + verifyStudentsExamAndExercisesAndQuizQuestions(exam, 0); + + generateMissingStudentExams(exam, 0); + verifyStudentsExamAndExercisesAndQuizQuestions(exam, 0); + } + + private void setupEmptyQuizPoolForExam(Exam exam) { + QuizPool quizPool = new QuizPool(); + quizPoolService.update(exam.getId(), quizPool); + } + + private void setupQuizPoolWithQuestionsForExam(Exam exam) { + QuizPool quizPool = new QuizPool(); + setupGroupsAndQuestionsForQuizPool(quizPool); + quizPoolService.update(exam.getId(), quizPool); + } + + private void setupGroupsAndQuestionsForQuizPool(QuizPool quizPool) { + QuizGroup quizGroup0 = QuizExerciseFactory.createQuizGroup("Encapsulation"); + QuizGroup quizGroup1 = QuizExerciseFactory.createQuizGroup("Inheritance"); + QuizGroup quizGroup2 = QuizExerciseFactory.createQuizGroup("Polymorphism"); + QuizQuestion mcQuizQuestion0 = QuizExerciseFactory.createMultipleChoiceQuestionWithTitleAndGroup("MC 0", quizGroup0); + QuizQuestion mcQuizQuestion1 = QuizExerciseFactory.createMultipleChoiceQuestionWithTitleAndGroup("MC 1", quizGroup0); + QuizQuestion dndQuizQuestion0 = QuizExerciseFactory.createDragAndDropQuestionWithTitleAndGroup("DND 0", quizGroup1); + QuizQuestion dndQuizQuestion1 = QuizExerciseFactory.createDragAndDropQuestionWithTitleAndGroup("DND 1", quizGroup2); + QuizQuestion saQuizQuestion0 = QuizExerciseFactory.createShortAnswerQuestionWithTitleAndGroup("SA 0", null); + quizPool.setQuizGroups(List.of(quizGroup0, quizGroup1, quizGroup2)); + quizPool.setQuizQuestions(List.of(mcQuizQuestion0, mcQuizQuestion1, dndQuizQuestion0, dndQuizQuestion1, saQuizQuestion0)); + } + + private void registerNewStudentsToExam(Exam exam, int numberOfStudents) { + examUtilService.registerUsersForExamAndSaveExam(exam, TEST_PREFIX, 2, 2 + numberOfStudents - 1); + } + + private void generateStudentExams(Exam exam) throws Exception { List studentExams = request.postListWithResponseBody("/api/courses/" + course1.getId() + "/exams/" + exam.getId() + "/generate-student-exams", Optional.empty(), StudentExam.class, HttpStatus.OK); - assertThat(studentExams).hasSize(exam.getExamUsers().size()); + for (var studentExam : studentExams) { + assertThat(studentExam.getExam()).isEqualTo(exam); + } + verifyStudentExams(studentExams, exam.getExamUsers().size()); + } + + private void generateMissingStudentExams(Exam exam, int expectedMissingStudent) throws Exception { + List missingStudentExams = request.postListWithResponseBody("/api/courses/" + course1.getId() + "/exams/" + exam.getId() + "/generate-missing-student-exams", + Optional.empty(), StudentExam.class, HttpStatus.OK); + assertThat(missingStudentExams).hasSize(expectedMissingStudent); + } + + private void verifyStudentsExamAndExercisesAndQuizQuestions(Exam exam, int numberOfQuizQuestions) throws Exception { + List studentExams = request.getList("/api/courses/" + course1.getId() + "/exams/" + exam.getId() + "/student-exams", HttpStatus.OK, StudentExam.class); + verifyStudentExams(studentExams, exam.getExamUsers().size()); + + verifyStudentExamsExercises(studentExams, exam.getNumberOfExercisesInExam()); + verifyStudentExamsQuizQuestions(studentExams, numberOfQuizQuestions); + } + + private void verifyStudentExams(List studentExams, int expectedNumberOfStudentExams) { + assertThat(studentExams).hasSize(expectedNumberOfStudentExams); for (StudentExam studentExam : studentExams) { assertThat(studentExam.getWorkingTime()).as("Working time is set correctly").isEqualTo(120 * 60); } + } - for (var studentExam : studentExams) { - assertThat(studentExam.getExercises()).hasSize(exam.getNumberOfExercisesInExam()); - assertThat(studentExam.getExam()).isEqualTo(exam); - // TODO: check exercise configuration, each mandatory exercise group has to appear, one optional exercise should appear + private void verifyStudentExamsExercises(List studentExams, int expected) { + List ids = studentExams.stream().map(StudentExam::getId).toList(); + List studentExamsWithExercises = studentExamRepository.findAllWithEagerExercisesById(ids); + for (var studentExam : studentExamsWithExercises) { + assertThat(studentExam.getExercises()).hasSize(expected); + } + // TODO: check exercise configuration, each mandatory exercise group has to appear, one optional exercise should appear + } + + private void verifyStudentExamsQuizQuestions(List studentExams, int expected) { + List ids = studentExams.stream().map(StudentExam::getId).toList(); + List studentExamsWithQuizQuestions = studentExamRepository.findAllWithEagerQuizQuestionsById(ids); + for (var studentExam : studentExamsWithQuizQuestions) { + assertThat(studentExam.getQuizQuestions()).hasSize(expected); } } @@ -280,36 +420,6 @@ void testGenerateStudentExamsTooManyMandatoryExerciseGroups_badRequest() throws HttpStatus.BAD_REQUEST); } - @Test - @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") - void testGenerateMissingStudentExams() throws Exception { - Exam exam = examUtilService.setupExamWithExerciseGroupsExercisesRegisteredStudents(TEST_PREFIX, course1, 1); - // Generate student exams - List studentExams = request.postListWithResponseBody("/api/courses/" + course1.getId() + "/exams/" + exam.getId() + "/generate-student-exams", - Optional.empty(), StudentExam.class, HttpStatus.OK); - assertThat(studentExams).hasSize(exam.getExamUsers().size()); - - // Register one new students - examUtilService.registerUsersForExamAndSaveExam(exam, TEST_PREFIX, 2, 2); - - // Generate individual exams for the two missing students - List missingStudentExams = request.postListWithResponseBody("/api/courses/" + course1.getId() + "/exams/" + exam.getId() + "/generate-missing-student-exams", - Optional.empty(), StudentExam.class, HttpStatus.OK); - assertThat(missingStudentExams).hasSize(1); - - // Fetch student exams - List studentExamsDB = request.getList("/api/courses/" + course1.getId() + "/exams/" + exam.getId() + "/student-exams", HttpStatus.OK, StudentExam.class); - assertThat(studentExamsDB).hasSize(exam.getExamUsers().size()); - - // Another request should not create any exams - missingStudentExams = request.postListWithResponseBody("/api/courses/" + course1.getId() + "/exams/" + exam.getId() + "/generate-missing-student-exams", Optional.empty(), - StudentExam.class, HttpStatus.OK); - assertThat(missingStudentExams).isEmpty(); - - studentExamsDB = request.getList("/api/courses/" + course1.getId() + "/exams/" + exam.getId() + "/student-exams", HttpStatus.OK, StudentExam.class); - assertThat(studentExamsDB).hasSize(exam.getExamUsers().size()); - } - @Test @WithMockUser(username = TEST_PREFIX + "admin", roles = "ADMIN") void testSaveExamWithExerciseGroupWithExerciseToDatabase() { diff --git a/src/test/java/de/tum/in/www1/artemis/service/exam/ExamQuizServiceTest.java b/src/test/java/de/tum/in/www1/artemis/service/exam/ExamQuizServiceTest.java index 4ebf28198f31..9b76b9aa528c 100644 --- a/src/test/java/de/tum/in/www1/artemis/service/exam/ExamQuizServiceTest.java +++ b/src/test/java/de/tum/in/www1/artemis/service/exam/ExamQuizServiceTest.java @@ -160,7 +160,7 @@ void evaluateQuiz() throws Exception { quizExercise = quizExerciseService.save(quizExercise); exerciseGroup.setExercises(Set.of(quizExercise)); - assertThat(studentExamRepository.generateStudentExams(exam)).hasSize(NUMBER_OF_STUDENTS); + assertThat(studentExamService.generateStudentExams(exam)).hasSize(NUMBER_OF_STUDENTS); assertThat(studentExamRepository.findByExamId(exam.getId())).hasSize(NUMBER_OF_STUDENTS); assertThat(studentExamService.startExercises(exam.getId()).join()).isEqualTo(NUMBER_OF_STUDENTS); @@ -205,7 +205,7 @@ void evaluateQuizWithNoSubmissions() throws Exception { quizExercise = quizExerciseService.save(quizExercise); exerciseGroup.setExercises(Set.of(quizExercise)); - assertThat(studentExamRepository.generateStudentExams(exam)).hasSize(NUMBER_OF_STUDENTS); + assertThat(studentExamService.generateStudentExams(exam)).hasSize(NUMBER_OF_STUDENTS); assertThat(studentExamRepository.findByExamId(exam.getId())).hasSize(NUMBER_OF_STUDENTS); // add participations with no submissions @@ -256,7 +256,7 @@ void evaluateQuizWithMultipleSubmissions() throws Exception { quizExercise = quizExerciseService.save(quizExercise); exerciseGroup.setExercises(Set.of(quizExercise)); - assertThat(studentExamRepository.generateStudentExams(exam)).hasSize(NUMBER_OF_STUDENTS); + assertThat(studentExamService.generateStudentExams(exam)).hasSize(NUMBER_OF_STUDENTS); assertThat(studentExamRepository.findByExamId(exam.getId())).hasSize(NUMBER_OF_STUDENTS); assertThat(studentExamService.startExercises(exam.getId()).join()).isEqualTo(NUMBER_OF_STUDENTS); @@ -308,7 +308,7 @@ void evaluateQuiz_twice() throws Exception { quizExercise = quizExerciseService.save(quizExercise); exerciseGroup.setExercises(Set.of(quizExercise)); - assertThat(studentExamRepository.generateStudentExams(exam)).hasSize(NUMBER_OF_STUDENTS); + assertThat(studentExamService.generateStudentExams(exam)).hasSize(NUMBER_OF_STUDENTS); assertThat(studentExamRepository.findByExamId(exam.getId())).hasSize(NUMBER_OF_STUDENTS); assertThat(studentExamService.startExercises(exam.getId()).join()).isEqualTo(NUMBER_OF_STUDENTS);

{{ isBonus ? ('artemisApp.exam.examSummary.bonus' | artemisTranslate) : ('artemisApp.exam.examSummary.grade' | artemisTranslate) }}