From ee3d5f8af9d58c715d1882c4f3db854d84449cb6 Mon Sep 17 00:00:00 2001 From: Laurenz Blumentritt <38919977+laurenzfb@users.noreply.github.com> Date: Sat, 4 May 2024 00:35:25 +0200 Subject: [PATCH 01/17] Integrated code lifecycle: Always create submission results for failed build jobs (#8534) --- .../tum/in/www1/artemis/domain/BuildJob.java | 2 +- .../LocalCIResultProcessingService.java | 87 +++++++++---------- .../localci/LocalCITriggerService.java | 37 ++++++-- .../buildagent/BuildJobExecutionService.java | 26 +++--- .../SharedQueueProcessingService.java | 6 +- .../connectors/localci/dto/BuildConfig.java | 5 +- .../localci/dto/LocalCIBuildResult.java | 10 +++ .../localvc/LocalVCServletService.java | 16 ++-- .../webapp/app/entities/build-config.model.ts | 2 +- .../build-agents/build-agents.component.html | 6 +- .../build-queue/build-queue.component.html | 12 +-- ...ctSpringIntegrationLocalCILocalVCTest.java | 2 + ...rogrammingExerciseTestCaseServiceTest.java | 3 + .../StaticCodeAnalysisIntegrationTest.java | 12 +++ .../localvcci/LocalCIDockerServiceTest.java | 2 +- .../localvcci/LocalCIIntegrationTest.java | 24 +++-- .../LocalCIResourceIntegrationTest.java | 2 +- .../LocalVCLocalCIIntegrationTest.java | 16 ++-- ... => StaticCodeAnalysisParserUnitTest.java} | 2 +- .../build-agents.component.spec.ts | 2 +- .../build-agents/build-agents.service.spec.ts | 2 +- .../build-queue/build-queue.component.spec.ts | 8 +- .../build-queue/build-queue.service.spec.ts | 2 +- 23 files changed, 166 insertions(+), 120 deletions(-) rename src/test/java/de/tum/in/www1/artemis/staticcodeanalysis/{StaticCodeAnalysisIntegrationTest.java => StaticCodeAnalysisParserUnitTest.java} (99%) diff --git a/src/main/java/de/tum/in/www1/artemis/domain/BuildJob.java b/src/main/java/de/tum/in/www1/artemis/domain/BuildJob.java index c367047ef0d0..fcf6ac5ba4a1 100644 --- a/src/main/java/de/tum/in/www1/artemis/domain/BuildJob.java +++ b/src/main/java/de/tum/in/www1/artemis/domain/BuildJob.java @@ -92,7 +92,7 @@ public BuildJob(LocalCIBuildJobQueueItem queueItem, BuildStatus buildStatus, Res this.buildCompletionDate = queueItem.jobTimingInfo().buildCompletionDate(); this.repositoryType = queueItem.repositoryInfo().repositoryType(); this.repositoryName = queueItem.repositoryInfo().repositoryName(); - this.commitHash = queueItem.buildConfig().commitHash(); + this.commitHash = queueItem.buildConfig().commitHashToBuild(); this.retryCount = queueItem.retryCount(); this.priority = queueItem.priority(); this.triggeredByPushTo = queueItem.repositoryInfo().triggeredByPushTo(); diff --git a/src/main/java/de/tum/in/www1/artemis/service/connectors/localci/LocalCIResultProcessingService.java b/src/main/java/de/tum/in/www1/artemis/service/connectors/localci/LocalCIResultProcessingService.java index 0fec1ada07d6..44228cc97328 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/connectors/localci/LocalCIResultProcessingService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/connectors/localci/LocalCIResultProcessingService.java @@ -138,14 +138,6 @@ public void processResult() { } result = programmingExerciseGradingService.processNewProgrammingExerciseResult(participation, buildResult); - if (result != null) { - programmingMessagingService.notifyUserAboutNewResult(result, participation); - addResultToBuildAgentsRecentBuildJobs(buildJob, result); - } - else { - programmingMessagingService.notifyUserAboutSubmissionError((Participation) participation, - new BuildTriggerWebsocketError("Result could not be processed", participation.getId())); - } } else { log.warn("Participation with id {} has been deleted. Cancelling the processing of the build result.", buildJob.participationId()); @@ -153,57 +145,56 @@ public void processResult() { } finally { // save build job to database - savedBuildJob = saveFinishedBuildJob(buildJob, BuildStatus.SUCCESSFUL, result); - } - } - else { - if (ex.getCause() instanceof CancellationException && ex.getMessage().equals("Build job with id " + buildJob.id() + " was cancelled.")) { + if (ex != null) { + if (ex.getCause() instanceof CancellationException && ex.getMessage().equals("Build job with id " + buildJob.id() + " was cancelled.")) { + savedBuildJob = saveFinishedBuildJob(buildJob, BuildStatus.CANCELLED, result); + } + else { + log.error("Error while processing build job: {}", buildJob, ex); + savedBuildJob = saveFinishedBuildJob(buildJob, BuildStatus.FAILED, result); + } + } + else { + savedBuildJob = saveFinishedBuildJob(buildJob, BuildStatus.SUCCESSFUL, result); + } if (participationOptional.isPresent()) { ProgrammingExerciseParticipation participation = (ProgrammingExerciseParticipation) participationOptional.get(); - programmingMessagingService.notifyUserAboutSubmissionError((Participation) participation, - new BuildTriggerWebsocketError("Build job was cancelled", participation.getId())); - } - savedBuildJob = saveFinishedBuildJob(buildJob, BuildStatus.CANCELLED, null); + if (result != null) { + programmingMessagingService.notifyUserAboutNewResult(result, participation); + addResultToBuildAgentsRecentBuildJobs(buildJob, result); + } + else { + programmingMessagingService.notifyUserAboutSubmissionError((Participation) participation, + new BuildTriggerWebsocketError("Result could not be processed", participation.getId())); + } + } } - else { - log.error("Error while processing build job: {}", buildJob, ex); - if (participationOptional.isPresent()) { - ProgrammingExerciseParticipation participation = (ProgrammingExerciseParticipation) participationOptional.get(); - programmingMessagingService.notifyUserAboutSubmissionError((Participation) participation, - new BuildTriggerWebsocketError(ex.getMessage(), participation.getId())); + if (!buildLogs.isEmpty()) { + if (savedBuildJob != null) { + buildLogEntryService.saveBuildLogsToFile(buildLogs, savedBuildJob.getBuildJobId()); } else { - log.warn("Participation with id {} has been deleted. Cancelling the requeueing of the build job.", buildJob.participationId()); + log.warn("Couldn't save build logs as build job {} was not saved", buildJob.id()); } - - savedBuildJob = saveFinishedBuildJob(buildJob, BuildStatus.FAILED, null); } - } - - if (!buildLogs.isEmpty()) { - if (savedBuildJob != null) { - buildLogEntryService.saveBuildLogsToFile(buildLogs, savedBuildJob.getBuildJobId()); - } - else { - log.warn("Couldn't save build logs as build job {} was not saved", buildJob.id()); - } - } - // If the build job is a solution build of a test or auxiliary push, we need to trigger the build of the corresponding template repository - if (isSolutionBuildOfTestOrAuxPush(buildJob)) { - log.debug("Triggering build of template repository for solution build with id {}", buildJob.id()); - try { - programmingTriggerService.triggerTemplateBuildAndNotifyUser(buildJob.exerciseId(), buildJob.buildConfig().commitHash(), SubmissionType.TEST, - buildJob.repositoryInfo().triggeredByPushTo()); - } - catch (EntityNotFoundException e) { - // Something went wrong while retrieving the template participation. - // At this point, programmingMessagingService.notifyUserAboutSubmissionError() does not work, because the template participation is not available. - // The instructor will see in the UI that no build of the template repository was conducted and will receive an error message when triggering the build manually. - log.error("Something went wrong while triggering the template build for exercise {} after the solution build was finished.", buildJob.exerciseId(), e); + // If the build job is a solution build of a test or auxiliary push, we need to trigger the build of the corresponding template repository + if (isSolutionBuildOfTestOrAuxPush(buildJob)) { + log.debug("Triggering build of template repository for solution build with id {}", buildJob.id()); + try { + programmingTriggerService.triggerTemplateBuildAndNotifyUser(buildJob.exerciseId(), buildJob.buildConfig().testCommitHash(), SubmissionType.TEST, + buildJob.repositoryInfo().triggeredByPushTo()); + } + catch (EntityNotFoundException e) { + // Something went wrong while retrieving the template participation. + // At this point, programmingMessagingService.notifyUserAboutSubmissionError() does not work, because the template participation is not available. + // The instructor will see in the UI that no build of the template repository was conducted and will receive an error message when triggering the build + // manually. + log.error("Something went wrong while triggering the template build for exercise {} after the solution build was finished.", buildJob.exerciseId(), e); + } } } } diff --git a/src/main/java/de/tum/in/www1/artemis/service/connectors/localci/LocalCITriggerService.java b/src/main/java/de/tum/in/www1/artemis/service/connectors/localci/LocalCITriggerService.java index 2caf5f18af5c..0b4d956c0e80 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/connectors/localci/LocalCITriggerService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/connectors/localci/LocalCITriggerService.java @@ -30,6 +30,7 @@ import de.tum.in.www1.artemis.exception.localvc.LocalVCInternalException; import de.tum.in.www1.artemis.repository.AuxiliaryRepositoryRepository; import de.tum.in.www1.artemis.repository.SolutionProgrammingExerciseParticipationRepository; +import de.tum.in.www1.artemis.service.connectors.GitService; import de.tum.in.www1.artemis.service.connectors.aeolus.AeolusResult; import de.tum.in.www1.artemis.service.connectors.aeolus.AeolusTemplateService; import de.tum.in.www1.artemis.service.connectors.aeolus.Windfile; @@ -66,6 +67,8 @@ public class LocalCITriggerService implements ContinuousIntegrationTriggerServic private final LocalCIBuildConfigurationService localCIBuildConfigurationService; + private final GitService gitService; + private IQueue queue; private IMap dockerImageCleanupInfo; @@ -74,7 +77,7 @@ public LocalCITriggerService(HazelcastInstance hazelcastInstance, AeolusTemplate ProgrammingLanguageConfiguration programmingLanguageConfiguration, AuxiliaryRepositoryRepository auxiliaryRepositoryRepository, LocalCIProgrammingLanguageFeatureService programmingLanguageFeatureService, Optional versionControlService, SolutionProgrammingExerciseParticipationRepository solutionProgrammingExerciseParticipationRepository, - LocalCIBuildConfigurationService localCIBuildConfigurationService) { + LocalCIBuildConfigurationService localCIBuildConfigurationService, GitService gitService) { this.hazelcastInstance = hazelcastInstance; this.aeolusTemplateService = aeolusTemplateService; this.programmingLanguageConfiguration = programmingLanguageConfiguration; @@ -83,6 +86,7 @@ public LocalCITriggerService(HazelcastInstance hazelcastInstance, AeolusTemplate this.versionControlService = versionControlService; this.solutionProgrammingExerciseParticipationRepository = solutionProgrammingExerciseParticipationRepository; this.localCIBuildConfigurationService = localCIBuildConfigurationService; + this.gitService = gitService; } @PostConstruct @@ -106,12 +110,31 @@ public void triggerBuild(ProgrammingExerciseParticipation participation) throws * Add a new build job item containing all relevant information necessary for the execution to the distributed build job queue. * * @param participation the participation of the repository which should be built and tested - * @param commitHash the commit hash of the commit that triggers the build. If it is null, the latest commit of the default branch will be built. + * @param commitHashToBuild the commit hash of the commit that triggers the build. If it is null, the latest commit of the default branch will be built. * @param triggeredByPushTo type of the repository that was pushed to and triggered the build job * @throws LocalCIException if the build job could not be added to the queue. */ @Override - public void triggerBuild(ProgrammingExerciseParticipation participation, String commitHash, RepositoryType triggeredByPushTo) throws LocalCIException { + public void triggerBuild(ProgrammingExerciseParticipation participation, String commitHashToBuild, RepositoryType triggeredByPushTo) throws LocalCIException { + + // Commit hash related to the repository that will be tested + String assignmentCommitHash; + + // Commit hash related to the test repository + String testCommitHash; + + if (triggeredByPushTo == null || triggeredByPushTo.equals(RepositoryType.AUXILIARY)) { + assignmentCommitHash = gitService.getLastCommitHash(participation.getVcsRepositoryUri()).getName(); + testCommitHash = gitService.getLastCommitHash(participation.getProgrammingExercise().getVcsTestRepositoryUri()).getName(); + } + else if (triggeredByPushTo.equals(RepositoryType.TESTS)) { + assignmentCommitHash = gitService.getLastCommitHash(participation.getVcsRepositoryUri()).getName(); + testCommitHash = commitHashToBuild; + } + else { + assignmentCommitHash = commitHashToBuild; + testCommitHash = gitService.getLastCommitHash(participation.getProgrammingExercise().getVcsTestRepositoryUri()).getName(); + } ProgrammingExercise programmingExercise = participation.getProgrammingExercise(); @@ -128,7 +151,7 @@ public void triggerBuild(ProgrammingExerciseParticipation participation, String RepositoryInfo repositoryInfo = getRepositoryInfo(participation, triggeredByPushTo); - BuildConfig buildConfig = getBuildConfig(participation, commitHash); + BuildConfig buildConfig = getBuildConfig(participation, commitHashToBuild, assignmentCommitHash, testCommitHash); LocalCIBuildJobQueueItem buildJobQueueItem = new LocalCIBuildJobQueueItem(buildJobId, participation.getBuildPlanId(), null, participation.getId(), courseId, programmingExercise.getId(), 0, priority, null, repositoryInfo, jobTimingInfo, buildConfig, null); @@ -211,7 +234,7 @@ else if (repositoryTypeOrUserName.equals("solution")) { } - private BuildConfig getBuildConfig(ProgrammingExerciseParticipation participation, String commitHash) { + private BuildConfig getBuildConfig(ProgrammingExerciseParticipation participation, String commitHashToBuild, String assignmentCommitHash, String testCommitHash) { String branch; try { branch = versionControlService.orElseThrow().getOrRetrieveBranchOfParticipation(participation); @@ -244,7 +267,7 @@ private BuildConfig getBuildConfig(ProgrammingExerciseParticipation participatio // Todo: If build agent does not have access to filesystem, we need to send the build script to the build agent and execute it there. String buildScript = localCIBuildConfigurationService.createBuildScript(participation); - return new BuildConfig(buildScript, dockerImage, commitHash, branch, programmingLanguage, projectType, staticCodeAnalysisEnabled, sequentialTestRunsEnabled, - testwiseCoverageEnabled, resultPaths); + return new BuildConfig(buildScript, dockerImage, commitHashToBuild, assignmentCommitHash, testCommitHash, branch, programmingLanguage, projectType, + staticCodeAnalysisEnabled, sequentialTestRunsEnabled, testwiseCoverageEnabled, resultPaths); } } diff --git a/src/main/java/de/tum/in/www1/artemis/service/connectors/localci/buildagent/BuildJobExecutionService.java b/src/main/java/de/tum/in/www1/artemis/service/connectors/localci/buildagent/BuildJobExecutionService.java index 78fa75eb484f..7e2136f7afe4 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/connectors/localci/buildagent/BuildJobExecutionService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/connectors/localci/buildagent/BuildJobExecutionService.java @@ -7,7 +7,6 @@ import java.io.IOException; import java.io.StringReader; -import java.net.URL; import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.nio.file.Paths; @@ -76,9 +75,6 @@ public class BuildJobExecutionService { private final BuildLogsMap buildLogsMap; - @Value("${artemis.version-control.url}") - private URL localVCBaseUrl; - @Value("${artemis.version-control.default-branch:main}") private String defaultBranch; @@ -134,7 +130,7 @@ public LocalCIBuildResult runBuildJob(LocalCIBuildJobQueueItem buildJob, String LocalVCRepositoryUri testsRepoUri = new LocalVCRepositoryUri(buildJob.repositoryInfo().testRepositoryUri()); // retrieve last commit hash from repositories - String assignmentCommitHash = buildJob.buildConfig().commitHash(); + String assignmentCommitHash = buildJob.buildConfig().assignmentCommitHash(); if (assignmentCommitHash == null) { try { assignmentCommitHash = gitService.getLastCommitHash(assignmentRepoUri).getName(); @@ -145,14 +141,16 @@ public LocalCIBuildResult runBuildJob(LocalCIBuildJobQueueItem buildJob, String throw new LocalCIException(msg, e); } } - String testCommitHash; - try { - testCommitHash = gitService.getLastCommitHash(testsRepoUri).getName(); - } - catch (EntityNotFoundException e) { - msg = "Could not find last commit hash for test repository " + testsRepoUri.repositorySlug(); - buildLogsMap.appendBuildLogEntry(buildJob.id(), msg); - throw new LocalCIException(msg, e); + String testCommitHash = buildJob.buildConfig().testCommitHash(); + if (testCommitHash == null) { + try { + testCommitHash = gitService.getLastCommitHash(testsRepoUri).getName(); + } + catch (EntityNotFoundException e) { + msg = "Could not find last commit hash for test repository " + testsRepoUri.repositorySlug(); + buildLogsMap.appendBuildLogEntry(buildJob.id(), msg); + throw new LocalCIException(msg, e); + } } Path assignmentRepositoryPath; @@ -161,7 +159,7 @@ public LocalCIBuildResult runBuildJob(LocalCIBuildJobQueueItem buildJob, String * If this build job is triggered by a push to the test repository, the commit hash reflects changes to the test repository. * Thus, we do not checkout the commit hash of the test repository in the assignment repository. */ - if (buildJob.buildConfig().commitHash() != null && !isPushToTestOrAuxRepository) { + if (buildJob.buildConfig().assignmentCommitHash() != null && !isPushToTestOrAuxRepository) { // Clone the assignment repository into a temporary directory with the name of the commit hash and then checkout the commit hash. assignmentRepositoryPath = cloneRepository(assignmentRepoUri, assignmentCommitHash, true, buildJob.id()); } diff --git a/src/main/java/de/tum/in/www1/artemis/service/connectors/localci/buildagent/SharedQueueProcessingService.java b/src/main/java/de/tum/in/www1/artemis/service/connectors/localci/buildagent/SharedQueueProcessingService.java index c5ffa19e637a..27a5f73bd5b5 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/connectors/localci/buildagent/SharedQueueProcessingService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/connectors/localci/buildagent/SharedQueueProcessingService.java @@ -311,7 +311,11 @@ private void processBuild(LocalCIBuildJobQueueItem buildJob) { List buildLogs = buildLogsMap.getBuildLogs(buildJob.id()); buildLogsMap.removeBuildLogs(buildJob.id()); - ResultQueueItem resultQueueItem = new ResultQueueItem(null, job, buildLogs, ex); + LocalCIBuildResult failedResult = new LocalCIBuildResult(buildJob.buildConfig().branch(), buildJob.buildConfig().assignmentCommitHash(), + buildJob.buildConfig().testCommitHash(), false); + failedResult.setBuildLogEntries(buildLogs); + + ResultQueueItem resultQueueItem = new ResultQueueItem(failedResult, job, buildLogs, ex); resultQueue.add(resultQueueItem); processingJobs.remove(buildJob.id()); diff --git a/src/main/java/de/tum/in/www1/artemis/service/connectors/localci/dto/BuildConfig.java b/src/main/java/de/tum/in/www1/artemis/service/connectors/localci/dto/BuildConfig.java index 063bc940bb27..284b5c383251 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/connectors/localci/dto/BuildConfig.java +++ b/src/main/java/de/tum/in/www1/artemis/service/connectors/localci/dto/BuildConfig.java @@ -11,8 +11,9 @@ @JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(JsonInclude.Include.NON_EMPTY) -public record BuildConfig(String buildScript, String dockerImage, String commitHash, String branch, ProgrammingLanguage programmingLanguage, ProjectType projectType, - boolean scaEnabled, boolean sequentialTestRunsEnabled, boolean testwiseCoverageEnabled, List resultPaths) implements Serializable { +public record BuildConfig(String buildScript, String dockerImage, String commitHashToBuild, String assignmentCommitHash, String testCommitHash, String branch, + ProgrammingLanguage programmingLanguage, ProjectType projectType, boolean scaEnabled, boolean sequentialTestRunsEnabled, boolean testwiseCoverageEnabled, + List resultPaths) implements Serializable { @Override public String dockerImage() { 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 d89094242004..10eb5e605b99 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 @@ -55,6 +55,16 @@ public LocalCIBuildResult(String assignmentRepoBranchName, String assignmentRepo this.staticCodeAnalysisReports = staticCodeAnalysisReports; } + public LocalCIBuildResult(String assignmentRepoBranchName, String assignmentRepoCommitHash, String testsRepoCommitHash, boolean isBuildSuccessful) { + this.assignmentRepoBranchName = assignmentRepoBranchName; + this.assignmentRepoCommitHash = assignmentRepoCommitHash; + this.testsRepoCommitHash = testsRepoCommitHash; + this.isBuildSuccessful = isBuildSuccessful; + this.buildRunDate = ZonedDateTime.now(); + this.jobs = new ArrayList<>(); + this.staticCodeAnalysisReports = new ArrayList<>(); + } + @Override public ZonedDateTime getBuildRunDate() { return buildRunDate; 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 34413ec7087a..a333bbd7f77b 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 @@ -363,15 +363,21 @@ public void processNewPush(String commitHash, Repository repository) { RepositoryType repositoryType = getRepositoryType(repositoryTypeOrUserName, exercise); try { - if (commitHash == null) { - commitHash = getLatestCommitHash(repository); + if (repositoryType.equals(RepositoryType.TESTS)) { + processNewPushToTestOrAuxRepository(exercise, commitHash, (SolutionProgrammingExerciseParticipation) participation, repositoryType); + return; } - if (repositoryType.equals(RepositoryType.TESTS) || repositoryType.equals(RepositoryType.AUXILIARY)) { - processNewPushToTestOrAuxRepository(exercise, commitHash, (SolutionProgrammingExerciseParticipation) participation, repositoryType); + if (repositoryType.equals(RepositoryType.AUXILIARY)) { + // Don't provide a commit hash because we want the latest test repo commit to be used + processNewPushToTestOrAuxRepository(exercise, null, (SolutionProgrammingExerciseParticipation) participation, repositoryType); return; } + if (commitHash == null) { + commitHash = getLatestCommitHash(repository); + } + Commit commit = extractCommitInfo(commitHash, repository); // Process push to any repository other than the test repository. @@ -434,7 +440,7 @@ private String getLatestCommitHash(Repository repository) throws GitAPIException * Build and test the solution repository to make sure all tests are still passing. * * @param exercise the exercise for which the push was made. - * @param commitHash the hash of the last commit to the test repository. + * @param commitHash the hash of the commit used as the last commit to the test repository. * @param repositoryType type of repository that has been pushed to * @throws VersionControlException if something unexpected goes wrong when creating the submission or triggering the build. */ diff --git a/src/main/webapp/app/entities/build-config.model.ts b/src/main/webapp/app/entities/build-config.model.ts index 0a7c08e13f2c..dcae29ec591f 100644 --- a/src/main/webapp/app/entities/build-config.model.ts +++ b/src/main/webapp/app/entities/build-config.model.ts @@ -1,6 +1,6 @@ export class BuildConfig { public dockerImage?: string; - public commitHash?: string; + public commitHashToBuild?: string; public branch?: string; public programmingLanguage?: string; public projectType?: string; diff --git a/src/main/webapp/app/localci/build-agents/build-agents.component.html b/src/main/webapp/app/localci/build-agents/build-agents.component.html index 35997cefae4c..2d8f22e20997 100644 --- a/src/main/webapp/app/localci/build-agents/build-agents.component.html +++ b/src/main/webapp/app/localci/build-agents/build-agents.component.html @@ -227,11 +227,11 @@
{{ agent.na {{ value }} - + - + - + 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 c06dd277bc27..18f76b588fdd 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 @@ -96,11 +96,11 @@

+ - + - + @@ -286,11 +286,11 @@

+ - + - + diff --git a/src/test/java/de/tum/in/www1/artemis/AbstractSpringIntegrationLocalCILocalVCTest.java b/src/test/java/de/tum/in/www1/artemis/AbstractSpringIntegrationLocalCILocalVCTest.java index d6a1d1d18c0a..b01bda5603eb 100644 --- a/src/test/java/de/tum/in/www1/artemis/AbstractSpringIntegrationLocalCILocalVCTest.java +++ b/src/test/java/de/tum/in/www1/artemis/AbstractSpringIntegrationLocalCILocalVCTest.java @@ -114,6 +114,8 @@ public abstract class AbstractSpringIntegrationLocalCILocalVCTest extends Abstra protected static final String DUMMY_COMMIT_HASH = "1234567890abcdef"; + protected static final String DUMMY_COMMIT_HASH_VALID = "9b3a9bd71a0d80e5bbc42204c319ed3d1d4f0d6d"; + private static final Path TEST_RESULTS_PATH = Path.of("src", "test", "resources", "test-data", "test-results"); private static final Path GRADLE_TEST_RESULTS_PATH = TEST_RESULTS_PATH.resolve("java-gradle"); diff --git a/src/test/java/de/tum/in/www1/artemis/exercise/programmingexercise/ProgrammingExerciseTestCaseServiceTest.java b/src/test/java/de/tum/in/www1/artemis/exercise/programmingexercise/ProgrammingExerciseTestCaseServiceTest.java index f42aedee7ffd..df2cefb9a0d5 100644 --- a/src/test/java/de/tum/in/www1/artemis/exercise/programmingexercise/ProgrammingExerciseTestCaseServiceTest.java +++ b/src/test/java/de/tum/in/www1/artemis/exercise/programmingexercise/ProgrammingExerciseTestCaseServiceTest.java @@ -144,6 +144,9 @@ void shouldAllowTestCaseWeightSumZero(AssessmentType assessmentType) throws Exce programmingExercise.setAssessmentType(assessmentType); programmingExerciseRepository.save(programmingExercise); + String dummyHash = "9b3a9bd71a0d80e5bbc42204c319ed3d1d4f0d6d"; + doReturn(ObjectId.fromString(dummyHash)).when(gitService).getLastCommitHash(any()); + var result = ProgrammingExerciseFactory.generateTestResultDTO(null, "SOLUTION", null, programmingExercise.getProgrammingLanguage(), false, List.of("test1", "test2", "test3"), Collections.emptyList(), null, null, null); feedbackCreationService.generateTestCasesFromBuildResult(result, programmingExercise); diff --git a/src/test/java/de/tum/in/www1/artemis/exercise/programmingexercise/StaticCodeAnalysisIntegrationTest.java b/src/test/java/de/tum/in/www1/artemis/exercise/programmingexercise/StaticCodeAnalysisIntegrationTest.java index 5e6ca794d55c..bba5b9214f0f 100644 --- a/src/test/java/de/tum/in/www1/artemis/exercise/programmingexercise/StaticCodeAnalysisIntegrationTest.java +++ b/src/test/java/de/tum/in/www1/artemis/exercise/programmingexercise/StaticCodeAnalysisIntegrationTest.java @@ -1,12 +1,15 @@ package de.tum.in.www1.artemis.exercise.programmingexercise; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; import java.time.ZonedDateTime; import java.util.List; import java.util.Set; import java.util.stream.Collectors; +import org.eclipse.jgit.lib.ObjectId; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -148,6 +151,9 @@ void testGetStaticCodeAnalysisCategories_staticCodeAnalysisNotEnabled_badRequest @EnumSource(value = ProgrammingLanguage.class, names = { "JAVA", "SWIFT", "C" }) @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") void testUpdateStaticCodeAnalysisCategories(ProgrammingLanguage programmingLanguage) throws Exception { + String dummyHash = "9b3a9bd71a0d80e5bbc42204c319ed3d1d4f0d6d"; + doReturn(ObjectId.fromString(dummyHash)).when(gitService).getLastCommitHash(any()); + var programmingExSCAEnabled = programmingExerciseUtilService.addCourseWithOneProgrammingExerciseAndStaticCodeAnalysisCategories(programmingLanguage); ProgrammingExercise exerciseWithSolutionParticipation = programmingExerciseRepository .findWithTemplateAndSolutionParticipationTeamAssignmentConfigCategoriesById(programmingExSCAEnabled.getId()).orElseThrow(); @@ -195,6 +201,9 @@ void testResetCategories_instructorInWrongCourse_forbidden() throws Exception { @EnumSource(value = ProgrammingLanguage.class, names = { "JAVA", "SWIFT", "C" }) @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") void testResetCategories(ProgrammingLanguage programmingLanguage) throws Exception { + String dummyHash = "9b3a9bd71a0d80e5bbc42204c319ed3d1d4f0d6d"; + doReturn(ObjectId.fromString(dummyHash)).when(gitService).getLastCommitHash(any()); + // Create a programming exercise with real categories var course = programmingExerciseUtilService.addCourseWithOneProgrammingExercise(true, false, programmingLanguage); ProgrammingExercise exercise = programmingExerciseRepository @@ -323,6 +332,9 @@ void shouldCategorizeFeedback() throws JsonProcessingException { @Test @WithMockUser(username = TEST_PREFIX + "editor1", roles = "EDITOR") void testImportCategories() throws Exception { + String dummyHash = "9b3a9bd71a0d80e5bbc42204c319ed3d1d4f0d6d"; + doReturn(ObjectId.fromString(dummyHash)).when(gitService).getLastCommitHash(any()); + ProgrammingExercise sourceExercise = programmingExerciseUtilService.addProgrammingExerciseToCourse(course, true); staticCodeAnalysisService.createDefaultCategories(sourceExercise); diff --git a/src/test/java/de/tum/in/www1/artemis/localvcci/LocalCIDockerServiceTest.java b/src/test/java/de/tum/in/www1/artemis/localvcci/LocalCIDockerServiceTest.java index 2c039a9a5054..837772beb3b8 100644 --- a/src/test/java/de/tum/in/www1/artemis/localvcci/LocalCIDockerServiceTest.java +++ b/src/test/java/de/tum/in/www1/artemis/localvcci/LocalCIDockerServiceTest.java @@ -92,7 +92,7 @@ void testPullDockerImage() { InspectImageCmd inspectImageCmd = mock(InspectImageCmd.class); doReturn(inspectImageCmd).when(dockerClient).inspectImageCmd(anyString()); doThrow(new NotFoundException("")).when(inspectImageCmd).exec(); - BuildConfig buildConfig = new BuildConfig("echo 'test'", "test-image-name", "test", "test", null, null, false, false, false, null); + BuildConfig buildConfig = new BuildConfig("echo 'test'", "test-image-name", "test", "test", "test", "test", null, null, false, false, false, null); var build = new LocalCIBuildJobQueueItem("1", "job1", "address1", 1, 1, 1, 1, 1, BuildStatus.SUCCESSFUL, null, null, buildConfig, null); // Pull image try { diff --git a/src/test/java/de/tum/in/www1/artemis/localvcci/LocalCIIntegrationTest.java b/src/test/java/de/tum/in/www1/artemis/localvcci/LocalCIIntegrationTest.java index a95cade6c092..1a0aa59a1ddf 100644 --- a/src/test/java/de/tum/in/www1/artemis/localvcci/LocalCIIntegrationTest.java +++ b/src/test/java/de/tum/in/www1/artemis/localvcci/LocalCIIntegrationTest.java @@ -27,6 +27,7 @@ import java.util.Optional; import java.util.Set; +import org.eclipse.jgit.lib.ObjectId; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -50,7 +51,6 @@ import de.tum.in.www1.artemis.domain.enumeration.BuildStatus; import de.tum.in.www1.artemis.domain.enumeration.ExerciseMode; import de.tum.in.www1.artemis.domain.enumeration.RepositoryType; -import de.tum.in.www1.artemis.domain.participation.Participation; import de.tum.in.www1.artemis.domain.participation.ProgrammingExerciseStudentParticipation; import de.tum.in.www1.artemis.exception.VersionControlException; import de.tum.in.www1.artemis.repository.BuildJobRepository; @@ -59,7 +59,6 @@ import de.tum.in.www1.artemis.service.connectors.localci.dto.ResultBuildJob; import de.tum.in.www1.artemis.service.connectors.localvc.LocalVCServletService; import de.tum.in.www1.artemis.util.LocalRepository; -import de.tum.in.www1.artemis.web.websocket.programmingSubmission.BuildTriggerWebsocketError; class LocalCIIntegrationTest extends AbstractLocalCILocalVCIntegrationTest { @@ -272,6 +271,9 @@ void testCannotFindResults() { @Test @WithMockUser(username = TEST_PREFIX + "student1", roles = "USER") void testIOExceptionWhenParsingTestResults() { + String dummyHash = "9b3a9bd71a0d80e5bbc42204c319ed3d1d4f0d6d"; + doReturn(ObjectId.fromString(dummyHash)).when(gitService).getLastCommitHash(any()); + ProgrammingExerciseStudentParticipation studentParticipation = localVCLocalCITestService.createParticipation(programmingExercise, student1Login); // Return an InputStream from dockerClient.copyArchiveFromContainerCmd().exec() such that repositoryTarInputStream.getNextTarEntry() throws an IOException. @@ -288,11 +290,10 @@ public int read() throws IOException { localVCServletService.processNewPush(commitHash, studentAssignmentRepository.originGit.getRepository()); - await().untilAsserted(() -> verify(programmingMessagingService).notifyUserAboutSubmissionError(Mockito.eq(studentParticipation), any())); + await().untilAsserted(() -> verify(programmingMessagingService).notifyUserAboutNewResult(any(), Mockito.eq(studentParticipation))); // Should notify the user. - verifyUserNotification(studentParticipation, - "java.util.concurrent.ExecutionException: de.tum.in.www1.artemis.exception.LocalCIException: Error while parsing test results"); + verifyUserNotification(studentParticipation); } @Test @@ -418,13 +419,10 @@ void testBuildLogs() throws IOException { } } - private void verifyUserNotification(Participation participation, String errorMessage) { - BuildTriggerWebsocketError expectedError = new BuildTriggerWebsocketError(errorMessage, participation.getId()); - await().untilAsserted( - () -> verify(programmingMessagingService).notifyUserAboutSubmissionError(Mockito.eq(participation), argThat((BuildTriggerWebsocketError actualError) -> { - assertThat(actualError.getError()).isEqualTo(expectedError.getError()); - assertThat(actualError.getParticipationId()).isEqualTo(expectedError.getParticipationId()); - return true; - }))); + private void verifyUserNotification(ProgrammingExerciseStudentParticipation participation) { + await().untilAsserted(() -> verify(programmingMessagingService).notifyUserAboutNewResult(argThat((Result result) -> { + assertThat(result.isSuccessful()).isFalse(); + return true; + }), Mockito.eq(participation))); } } diff --git a/src/test/java/de/tum/in/www1/artemis/localvcci/LocalCIResourceIntegrationTest.java b/src/test/java/de/tum/in/www1/artemis/localvcci/LocalCIResourceIntegrationTest.java index 5a784aa7a89e..1e856ecbd34d 100644 --- a/src/test/java/de/tum/in/www1/artemis/localvcci/LocalCIResourceIntegrationTest.java +++ b/src/test/java/de/tum/in/www1/artemis/localvcci/LocalCIResourceIntegrationTest.java @@ -80,7 +80,7 @@ void createJobs() { sharedQueueProcessingService.removeListener(); JobTimingInfo jobTimingInfo = new JobTimingInfo(ZonedDateTime.now(), ZonedDateTime.now().plusMinutes(1), ZonedDateTime.now().plusMinutes(2)); - BuildConfig buildConfig = new BuildConfig("echo 'test'", "test", "test", "test", null, null, false, false, false, null); + BuildConfig buildConfig = new BuildConfig("echo 'test'", "test", "test", "test", "test", "test", null, null, false, false, false, null); RepositoryInfo repositoryInfo = new RepositoryInfo("test", null, RepositoryType.USER, "test", "test", "test", null, null); job1 = new LocalCIBuildJobQueueItem("1", "job1", "address1", 1, course.getId(), 1, 1, 1, BuildStatus.SUCCESSFUL, repositoryInfo, jobTimingInfo, buildConfig, null); diff --git a/src/test/java/de/tum/in/www1/artemis/localvcci/LocalVCLocalCIIntegrationTest.java b/src/test/java/de/tum/in/www1/artemis/localvcci/LocalVCLocalCIIntegrationTest.java index 777b192df63c..c3b8fa83cd07 100644 --- a/src/test/java/de/tum/in/www1/artemis/localvcci/LocalVCLocalCIIntegrationTest.java +++ b/src/test/java/de/tum/in/www1/artemis/localvcci/LocalVCLocalCIIntegrationTest.java @@ -6,6 +6,7 @@ import static org.awaitility.Awaitility.await; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import java.io.IOException; @@ -20,6 +21,7 @@ import javax.naming.ldap.LdapName; import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.lib.ObjectId; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -267,13 +269,9 @@ void testFetchPush_auxiliaryRepository() throws Exception { // Instructors should be able to fetch and push. localVCLocalCITestService.testFetchSuccessful(auxiliaryRepository.localGit, instructor1Login, projectKey1, auxiliaryRepositorySlug); - String commitHash = localVCLocalCITestService.commitFile(auxiliaryRepository.localRepoFile.toPath(), auxiliaryRepository.localGit); + localVCLocalCITestService.commitFile(auxiliaryRepository.localRepoFile.toPath(), auxiliaryRepository.localGit); - // Mock dockerClient.copyArchiveFromContainerCmd() such that it returns the commitHash of the tests repository for both the solution and the template repository. - // Note: The stub needs to receive the same object twice. Usually, specifying one doReturn() is enough to make the stub return the same object on every subsequent call. - // However, in this case we have it return an InputStream, which will be consumed after returning it the first time, so we need to create two separate ones. - localVCLocalCITestService.mockInputStreamReturnedFromContainer(dockerClient, LOCALCI_WORKING_DIRECTORY + "/testing-dir/.git/refs/heads/[^/]+", - Map.of("testCommitHash", commitHash), Map.of("testCommitHash", commitHash)); + doReturn(ObjectId.fromString(DUMMY_COMMIT_HASH_VALID)).when(gitService).getLastCommitHash(eq(programmingExercise.getVcsTestRepositoryUri())); // Mock dockerClient.copyArchiveFromContainerCmd() such that it returns the XMLs containing the test results. // Mock the results for the solution repository build and for the template repository build that will both be triggered as a result of updating the tests. @@ -284,9 +282,9 @@ void testFetchPush_auxiliaryRepository() throws Exception { localVCLocalCITestService.testPushSuccessful(auxiliaryRepository.localGit, instructor1Login, projectKey1, auxiliaryRepositorySlug); - // Solution submissions created as a result from a push to the auxiliary repository should contain the last commit of the tests repository. - localVCLocalCITestService.testLatestSubmission(solutionParticipation.getId(), commitHash, 13, false); - localVCLocalCITestService.testLatestSubmission(templateParticipation.getId(), commitHash, 0, false); + // Solution submissions created as a result from a push to the auxiliary repository should contain the last commit of the test repository. + localVCLocalCITestService.testLatestSubmission(solutionParticipation.getId(), DUMMY_COMMIT_HASH_VALID, 13, false); + localVCLocalCITestService.testLatestSubmission(templateParticipation.getId(), DUMMY_COMMIT_HASH_VALID, 0, false); await().until(() -> { Optional buildJobOptional = buildJobRepository.findFirstByParticipationIdOrderByBuildStartDateDesc(templateParticipation.getId()); diff --git a/src/test/java/de/tum/in/www1/artemis/staticcodeanalysis/StaticCodeAnalysisIntegrationTest.java b/src/test/java/de/tum/in/www1/artemis/staticcodeanalysis/StaticCodeAnalysisParserUnitTest.java similarity index 99% rename from src/test/java/de/tum/in/www1/artemis/staticcodeanalysis/StaticCodeAnalysisIntegrationTest.java rename to src/test/java/de/tum/in/www1/artemis/staticcodeanalysis/StaticCodeAnalysisParserUnitTest.java index 951a37187635..3b3d251ddf60 100644 --- a/src/test/java/de/tum/in/www1/artemis/staticcodeanalysis/StaticCodeAnalysisIntegrationTest.java +++ b/src/test/java/de/tum/in/www1/artemis/staticcodeanalysis/StaticCodeAnalysisParserUnitTest.java @@ -23,7 +23,7 @@ /** * Tests each parser with an example file */ -class StaticCodeAnalysisIntegrationTest { +class StaticCodeAnalysisParserUnitTest { private static final Path EXPECTED_FOLDER_PATH = Paths.get("src", "test", "resources", "test-data", "static-code-analysis", "expected"); diff --git a/src/test/javascript/spec/component/localci/build-agents/build-agents.component.spec.ts b/src/test/javascript/spec/component/localci/build-agents/build-agents.component.spec.ts index dbccad690049..59574d66ba3c 100644 --- a/src/test/javascript/spec/component/localci/build-agents/build-agents.component.spec.ts +++ b/src/test/javascript/spec/component/localci/build-agents/build-agents.component.spec.ts @@ -56,7 +56,7 @@ describe('BuildAgentsComponent', () => { const buildConfig: BuildConfig = { dockerImage: 'someImage', - commitHash: 'abc124', + commitHashToBuild: 'abc124', branch: 'main', programmingLanguage: 'Java', projectType: 'Maven', diff --git a/src/test/javascript/spec/component/localci/build-agents/build-agents.service.spec.ts b/src/test/javascript/spec/component/localci/build-agents/build-agents.service.spec.ts index 24b65d1bacaf..2b5899bc68d9 100644 --- a/src/test/javascript/spec/component/localci/build-agents/build-agents.service.spec.ts +++ b/src/test/javascript/spec/component/localci/build-agents/build-agents.service.spec.ts @@ -34,7 +34,7 @@ describe('BuildAgentsService', () => { const buildConfig: BuildConfig = { dockerImage: 'someImage', - commitHash: 'abc124', + commitHashToBuild: 'abc124', branch: 'main', programmingLanguage: 'Java', projectType: 'Maven', diff --git a/src/test/javascript/spec/component/localci/build-queue/build-queue.component.spec.ts b/src/test/javascript/spec/component/localci/build-queue/build-queue.component.spec.ts index 981de88e55e1..48a69e0ccfa4 100644 --- a/src/test/javascript/spec/component/localci/build-queue/build-queue.component.spec.ts +++ b/src/test/javascript/spec/component/localci/build-queue/build-queue.component.spec.ts @@ -66,7 +66,7 @@ describe('BuildQueueComponent', () => { }, buildConfig: { dockerImage: 'someImage', - commitHash: 'abc123', + commitHashToBuild: 'abc123', branch: 'main', programmingLanguage: 'Java', projectType: 'Maven', @@ -102,7 +102,7 @@ describe('BuildQueueComponent', () => { }, buildConfig: { dockerImage: 'someImage', - commitHash: 'abc125', + commitHashToBuild: 'abc125', branch: 'main', programmingLanguage: 'Java', projectType: 'Maven', @@ -140,7 +140,7 @@ describe('BuildQueueComponent', () => { }, buildConfig: { dockerImage: 'someImage', - commitHash: 'abc124', + commitHashToBuild: 'abc124', branch: 'main', programmingLanguage: 'Java', projectType: 'Maven', @@ -176,7 +176,7 @@ describe('BuildQueueComponent', () => { }, buildConfig: { dockerImage: 'someImage', - commitHash: 'abc126', + commitHashToBuild: 'abc126', branch: 'main', programmingLanguage: 'Java', projectType: 'Maven', diff --git a/src/test/javascript/spec/component/localci/build-queue/build-queue.service.spec.ts b/src/test/javascript/spec/component/localci/build-queue/build-queue.service.spec.ts index 5a0f6a81df29..24bbf9a3de70 100644 --- a/src/test/javascript/spec/component/localci/build-queue/build-queue.service.spec.ts +++ b/src/test/javascript/spec/component/localci/build-queue/build-queue.service.spec.ts @@ -53,7 +53,7 @@ describe('BuildQueueService', () => { jobTimingInfo.buildCompletionDate = dayjs('2023-01-02'); buildConfig.dockerImage = 'image1'; - buildConfig.commitHash = 'hash1'; + buildConfig.commitHashToBuild = 'hash1'; buildConfig.branch = 'branch1'; buildConfig.programmingLanguage = 'lang1'; buildConfig.projectType = 'type1'; From d0f3e1ba3ee8e32ac1ecdabca38a885dfac4afc2 Mon Sep 17 00:00:00 2001 From: Ramona Beinstingel <75392103+rabeatwork@users.noreply.github.com> Date: Sat, 4 May 2024 00:38:40 +0200 Subject: [PATCH 02/17] General: Reduce navigation bar height (#8539) --- .../app/core/theme/theme-switch.component.scss | 2 +- .../webapp/app/shared/layouts/navbar/navbar.scss | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/webapp/app/core/theme/theme-switch.component.scss b/src/main/webapp/app/core/theme/theme-switch.component.scss index 0318a4c76022..1eebc439782a 100644 --- a/src/main/webapp/app/core/theme/theme-switch.component.scss +++ b/src/main/webapp/app/core/theme/theme-switch.component.scss @@ -27,7 +27,7 @@ $ease-out: cubic-bezier(0.9, 0, 0.1, 1); transform: translateY(-0.5px); & > svg { - inline-size: 100%; + inline-size: 90%; block-size: 100%; stroke-linecap: round; } diff --git a/src/main/webapp/app/shared/layouts/navbar/navbar.scss b/src/main/webapp/app/shared/layouts/navbar/navbar.scss index 928d0d4614cc..1a825b244dae 100644 --- a/src/main/webapp/app/shared/layouts/navbar/navbar.scss +++ b/src/main/webapp/app/shared/layouts/navbar/navbar.scss @@ -102,21 +102,21 @@ a:not(.btn):hover { flex: 1; align-items: center; flex-wrap: nowrap; - gap: 10px; - height: 50px; + gap: 0.5rem; + height: 2rem; .navbar-brand { display: flex; - gap: 10px; - padding: 10px 15px; + gap: 0.5rem; + padding: 0.75rem 0; align-items: center; margin-right: 0; height: 100%; img { - height: 30px; + height: 1.5rem; vertical-align: middle; - margin-right: 10px; + margin-right: 0.5rem; } .navbar-version { From 43c7372ea928442c7a5b5f821f7a9f02fe404071 Mon Sep 17 00:00:00 2001 From: Ramona Beinstingel <75392103+rabeatwork@users.noreply.github.com> Date: Sat, 4 May 2024 10:09:44 +0200 Subject: [PATCH 03/17] Programming exercises: Display automatic assessment history correctly (#8542) --- .../sidebar-card/sidebar-card.component.html | 2 +- .../sidebar-card/sidebar-card.component.ts | 15 ++++++++++++++- .../shared/sidebar/sidebar-card.component.spec.ts | 2 ++ .../javascript/spec/helpers/mocks/mock-router.ts | 2 +- 4 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/main/webapp/app/shared/sidebar/sidebar-card/sidebar-card.component.html b/src/main/webapp/app/shared/sidebar/sidebar-card/sidebar-card.component.html index 6ba830f56605..a181a53fcd66 100644 --- a/src/main/webapp/app/shared/sidebar/sidebar-card/sidebar-card.component.html +++ b/src/main/webapp/app/shared/sidebar/sidebar-card/sidebar-card.component.html @@ -8,7 +8,7 @@ 'border-module': !sidebarItem?.difficulty }" [routerLink]="itemSelected ? '../' + sidebarItem?.id : './' + sidebarItem?.id" - (click)="emitStoreLastSelectedItem(sidebarItem.id)" + (click)="emitStoreLastSelectedItem(sidebarItem.id); forceReload()" [routerLinkActive]="'bg-selected border-selected'" > diff --git a/src/main/webapp/app/shared/sidebar/sidebar-card/sidebar-card.component.ts b/src/main/webapp/app/shared/sidebar/sidebar-card/sidebar-card.component.ts index 40d7e336412e..83fb0a5b7007 100644 --- a/src/main/webapp/app/shared/sidebar/sidebar-card/sidebar-card.component.ts +++ b/src/main/webapp/app/shared/sidebar/sidebar-card/sidebar-card.component.ts @@ -3,6 +3,7 @@ import { DifficultyLevel } from 'app/entities/exercise.model'; import { SidebarCardElement, SidebarTypes } from 'app/types/sidebar'; import { Subscription } from 'rxjs'; import { SidebarEventService } from '../sidebar-event.service'; +import { ActivatedRoute, Router } from '@angular/router'; @Component({ selector: 'jhi-sidebar-card', templateUrl: './sidebar-card.component.html', @@ -19,9 +20,21 @@ export class SidebarCardComponent { paramSubscription?: Subscription; noItemSelected: boolean = false; - constructor(private sidebarEventService: SidebarEventService) {} + constructor( + private sidebarEventService: SidebarEventService, + private router: Router, + private route: ActivatedRoute, + ) {} emitStoreLastSelectedItem(itemId: number | string) { this.sidebarEventService.emitSidebarCardEvent(itemId); } + + forceReload(): void { + this.router.navigate(['../'], { skipLocationChange: true, relativeTo: this.route }).then(() => { + this.itemSelected + ? this.router.navigate(['../' + this.sidebarItem?.id], { relativeTo: this.route }) + : this.router.navigate(['./' + this.sidebarItem?.id], { relativeTo: this.route }); + }); + } } diff --git a/src/test/javascript/spec/component/shared/sidebar/sidebar-card.component.spec.ts b/src/test/javascript/spec/component/shared/sidebar/sidebar-card.component.spec.ts index ccb86f5d6d06..4a551dfeb965 100644 --- a/src/test/javascript/spec/component/shared/sidebar/sidebar-card.component.spec.ts +++ b/src/test/javascript/spec/component/shared/sidebar/sidebar-card.component.spec.ts @@ -61,10 +61,12 @@ describe('SidebarCardComponent', () => { it('should store route on click', () => { jest.spyOn(component, 'emitStoreLastSelectedItem'); + jest.spyOn(component, 'forceReload'); const element: HTMLElement = fixture.nativeElement.querySelector('#test-sidebar-card'); element.click(); fixture.detectChanges(); expect(component.emitStoreLastSelectedItem).toHaveBeenCalledWith(component.sidebarItem.id); + expect(component.forceReload).toHaveBeenCalled(); }); it('should navigate to the item URL on click', async () => { diff --git a/src/test/javascript/spec/helpers/mocks/mock-router.ts b/src/test/javascript/spec/helpers/mocks/mock-router.ts index 9a365795a9ad..9d04c0e57d04 100644 --- a/src/test/javascript/spec/helpers/mocks/mock-router.ts +++ b/src/test/javascript/spec/helpers/mocks/mock-router.ts @@ -5,7 +5,7 @@ import { NavigationEnd, RouterEvent, RouterState, UrlTree } from '@angular/route export class MockRouter { url = '/'; navigateByUrl = jest.fn().mockReturnValue(true); - navigate = jest.fn().mockReturnValue(true); + navigate = jest.fn().mockReturnValue(Promise.resolve(true)); routerState: RouterState; createUrlTree = jest.fn().mockReturnValue({ path: 'testValue' } as unknown as UrlTree); serializeUrl = jest.fn().mockReturnValue('testValue'); From c7f476653d4178c3aca5107d115af6eaa1b61290 Mon Sep 17 00:00:00 2001 From: Stephan Krusche Date: Sat, 4 May 2024 15:54:01 +0200 Subject: [PATCH 04/17] Development: Reduce noise during server startup --- .../Artemis__Server__LocalVC___LocalCI_.xml | 2 +- gradle.properties | 8 +++++++- .../EurekaClientRestTemplateConfiguration.java | 2 +- .../de/tum/in/www1/artemis/config/MetricsBean.java | 6 +++--- .../config/ProgrammingLanguageConfiguration.java | 4 ++-- .../de/tum/in/www1/artemis/config/WebConfigurer.java | 4 ++-- .../config/localvcci/LocalCIConfiguration.java | 2 +- .../artemis/config/migration/MigrationService.java | 12 ++++++------ .../config/websocket/WebsocketConfiguration.java | 4 ++-- .../EnforceAtLeastEditorInCourse.java | 2 ++ .../EnforceAtLeastTutorInCourse.java | 2 ++ .../EnforceAtLeastInstructorInExercise.java | 2 ++ .../tum/in/www1/artemis/service/PlantUmlService.java | 4 ++-- .../artemis/service/TitleCacheEvictionService.java | 2 +- .../www1/artemis/service/connectors/GitService.java | 8 ++++---- 15 files changed, 38 insertions(+), 26 deletions(-) diff --git a/.idea/runConfigurations/Artemis__Server__LocalVC___LocalCI_.xml b/.idea/runConfigurations/Artemis__Server__LocalVC___LocalCI_.xml index 3cacc929df9a..bf04b8954c1b 100644 --- a/.idea/runConfigurations/Artemis__Server__LocalVC___LocalCI_.xml +++ b/.idea/runConfigurations/Artemis__Server__LocalVC___LocalCI_.xml @@ -5,7 +5,7 @@ diff --git a/src/main/webapp/app/admin/lti-configuration/lti-configuration.component.ts b/src/main/webapp/app/admin/lti-configuration/lti-configuration.component.ts index f0e0f1f86869..2d978673adbc 100644 --- a/src/main/webapp/app/admin/lti-configuration/lti-configuration.component.ts +++ b/src/main/webapp/app/admin/lti-configuration/lti-configuration.component.ts @@ -1,14 +1,16 @@ import { Component, OnInit } from '@angular/core'; -import { Router } from '@angular/router'; +import { ActivatedRoute, Router } from '@angular/router'; import { Course } from 'app/entities/course.model'; import { faExclamationTriangle, faPencilAlt, faPlus, faSort, faTrash, faWrench } from '@fortawesome/free-solid-svg-icons'; import { LtiPlatformConfiguration } from 'app/admin/lti-configuration/lti-configuration.model'; import { LtiConfigurationService } from 'app/admin/lti-configuration/lti-configuration.service'; import { SortService } from 'app/shared/service/sort.service'; import { Subject } from 'rxjs'; -import { HttpErrorResponse } from '@angular/common/http'; +import { HttpErrorResponse, HttpHeaders, HttpResponse } from '@angular/common/http'; import { AlertService } from 'app/core/util/alert.service'; import { LTI_URLS } from 'app/admin/lti-configuration/lti-configuration.urls'; +import { ITEMS_PER_PAGE } from 'app/shared/constants/pagination.constants'; +import { combineLatest } from 'rxjs'; @Component({ selector: 'jhi-lti-configuration', @@ -17,12 +19,16 @@ import { LTI_URLS } from 'app/admin/lti-configuration/lti-configuration.urls'; export class LtiConfigurationComponent implements OnInit { course: Course; platforms: LtiPlatformConfiguration[]; - + ascending!: boolean; activeTab = 1; - predicate = 'id'; reverse = false; + // page information + page = 1; + itemsPerPage = ITEMS_PER_PAGE; + totalItems = 0; + // Icons faSort = faSort; faExclamationTriangle = faExclamationTriangle; @@ -39,16 +45,39 @@ export class LtiConfigurationComponent implements OnInit { private ltiConfigurationService: LtiConfigurationService, private sortService: SortService, private alertService: AlertService, + private activatedRoute: ActivatedRoute, ) {} /** * Gets the configuration for the course encoded in the route and fetches the exercises */ - ngOnInit() { - this.ltiConfigurationService.findAll().subscribe((configuredLtiPlatforms) => { - if (configuredLtiPlatforms) { - this.platforms = configuredLtiPlatforms; - } + ngOnInit(): void { + combineLatest({ data: this.activatedRoute.data, params: this.activatedRoute.queryParamMap }).subscribe(({ data, params }) => { + const page = params.get('page'); + this.page = page !== null ? +page : 1; + const sort = (params.get('sort') ?? data['defaultSort']).split(','); + this.predicate = sort[0]; + this.ascending = sort[1] === 'asc'; + this.loadData(); + }); + } + + loadData(): void { + this.ltiConfigurationService + .query({ + page: this.page - 1, + size: this.itemsPerPage, + sort: this.sort(), + }) + .subscribe((res: HttpResponse) => this.onSuccess(res.body, res.headers)); + } + + transition(): void { + this.router.navigate(['/admin/lti-configuration'], { + queryParams: { + page: this.page, + sort: this.predicate + ',' + (this.ascending ? 'asc' : 'desc'), + }, }); } @@ -120,4 +149,17 @@ export class LtiConfigurationComponent implements OnInit { }, }); } + + private sort(): string[] { + const result = [this.predicate + ',' + (this.ascending ? 'asc' : 'desc')]; + if (this.predicate !== 'id') { + result.push('id'); + } + return result; + } + + private onSuccess(platforms: LtiPlatformConfiguration[] | null, headers: HttpHeaders): void { + this.totalItems = Number(headers.get('X-Total-Count')); + this.platforms = platforms || []; + } } diff --git a/src/main/webapp/app/admin/lti-configuration/lti-configuration.route.ts b/src/main/webapp/app/admin/lti-configuration/lti-configuration.route.ts index 0fd2354ca669..aa663034e8bf 100644 --- a/src/main/webapp/app/admin/lti-configuration/lti-configuration.route.ts +++ b/src/main/webapp/app/admin/lti-configuration/lti-configuration.route.ts @@ -10,6 +10,7 @@ export const ltiConfigurationRoute: Routes = [ component: LtiConfigurationComponent, data: { pageTitle: 'global.menu.admin.lti', + defaultSort: 'id,desc', }, }, { diff --git a/src/main/webapp/app/admin/lti-configuration/lti-configuration.service.ts b/src/main/webapp/app/admin/lti-configuration/lti-configuration.service.ts index 2760f4e4336d..c6484edd768f 100644 --- a/src/main/webapp/app/admin/lti-configuration/lti-configuration.service.ts +++ b/src/main/webapp/app/admin/lti-configuration/lti-configuration.service.ts @@ -1,7 +1,8 @@ import { Injectable } from '@angular/core'; -import { HttpClient, HttpResponse } from '@angular/common/http'; +import { HttpClient, HttpParams, HttpResponse } from '@angular/common/http'; import { Observable } from 'rxjs'; import { LtiPlatformConfiguration } from 'app/admin/lti-configuration/lti-configuration.model'; +import { createRequestOption } from 'app/shared/util/request.util'; @Injectable({ providedIn: 'root' }) export class LtiConfigurationService { @@ -10,8 +11,12 @@ export class LtiConfigurationService { /** * Sends a GET request to retrieve all lti platform configurations */ - findAll(): Observable { - return this.http.get('api/lti-platforms'); + query(req?: any): Observable> { + const params: HttpParams = createRequestOption(req); + return this.http.get('api/lti-platforms', { + params, + observe: 'response', + }); } /** diff --git a/src/main/webapp/app/course/manage/course-lti-configuration/edit-course-lti-configuration.component.html b/src/main/webapp/app/course/manage/course-lti-configuration/edit-course-lti-configuration.component.html index c30f880de55d..85a65e3bf46f 100644 --- a/src/main/webapp/app/course/manage/course-lti-configuration/edit-course-lti-configuration.component.html +++ b/src/main/webapp/app/course/manage/course-lti-configuration/edit-course-lti-configuration.component.html @@ -59,6 +59,23 @@