diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ba23a4d736bc..4442571b230c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -119,7 +119,7 @@ jobs: name: Coverage Report Server Tests path: build/reports/jacoco/test/html - name: Append Per-Module Coverage to Job Summary - if: success() || failure() + if: success() run: | AGGREGATED_REPORT_FILE=./module_coverage_report.md python3 ./supporting_scripts/code-coverage/per_module_cov_report/parse_module_coverage.py build/reports/jacoco $AGGREGATED_REPORT_FILE diff --git a/docs/user/exercises/programming-exercise-features.inc b/docs/user/exercises/programming-exercise-features.inc index 2c29ff79a1f4..8ba8ed672b07 100644 --- a/docs/user/exercises/programming-exercise-features.inc +++ b/docs/user/exercises/programming-exercise-features.inc @@ -49,6 +49,10 @@ Instructors can still use those templates to generate programming exercises and +----------------------+----------+---------+ | Bash | yes | yes | +----------------------+----------+---------+ + | MATLAB | yes | no | + +----------------------+----------+---------+ + | Ruby | yes | yes | + +----------------------+----------+---------+ - Not all ``templates`` support the same feature set and supported features can also change depending on the continuous integration system setup. Depending on the feature set, some options might not be available during the creation of the programming exercise. @@ -95,6 +99,10 @@ Instructors can still use those templates to generate programming exercises and +----------------------+----------------------+----------------------+---------------------+--------------+------------------------------------------+------------------------------+------------------------+ | Bash | no | no | no | no | n/a | no | L: yes, J: no | +----------------------+----------------------+----------------------+---------------------+--------------+------------------------------------------+------------------------------+------------------------+ + | MATLAB | no | no | no | no | n/a | no | L: yes, J: no | + +----------------------+----------------------+----------------------+---------------------+--------------+------------------------------------------+------------------------------+------------------------+ + | Ruby | no | no | no | no | n/a | no | L: yes, J: no | + +----------------------+----------------------+----------------------+---------------------+--------------+------------------------------------------+------------------------------+------------------------+ - *Sequential Test Runs*: ``Artemis`` can generate a build plan which first executes structural and then behavioral tests. This feature can help students to better concentrate on the immediate challenge at hand. - *Static Code Analysis*: ``Artemis`` can generate a build plan which additionally executes static code analysis tools. diff --git a/gradle/jacoco.gradle b/gradle/jacoco.gradle index ceb43a8d5ec6..a76db0977c6e 100644 --- a/gradle/jacoco.gradle +++ b/gradle/jacoco.gradle @@ -83,10 +83,10 @@ ext { ? ModuleCoverageThresholds.collect {element -> element.key} : includedModules as ArrayList - // we want to ignore some generated files in the domain folders ignoredDirectories = [ "**/$BasePath/**/domain/**/*_*", "**/$BasePath/core/config/migration/entries/**", + "**/org/gradle/**", "**/gradle-wrapper.jar/**" ] } diff --git a/src/main/java/de/tum/cit/aet/artemis/ArtemisApp.java b/src/main/java/de/tum/cit/aet/artemis/ArtemisApp.java index 11b1e7c67636..b56b0f82990d 100644 --- a/src/main/java/de/tum/cit/aet/artemis/ArtemisApp.java +++ b/src/main/java/de/tum/cit/aet/artemis/ArtemisApp.java @@ -18,13 +18,14 @@ import org.springframework.boot.info.GitProperties; import org.springframework.core.env.Environment; +import de.tum.cit.aet.artemis.core.config.LicenseConfiguration; import de.tum.cit.aet.artemis.core.config.ProgrammingLanguageConfiguration; import de.tum.cit.aet.artemis.core.config.TheiaConfiguration; import tech.jhipster.config.DefaultProfileUtil; import tech.jhipster.config.JHipsterConstants; @SpringBootApplication -@EnableConfigurationProperties({ LiquibaseProperties.class, ProgrammingLanguageConfiguration.class, TheiaConfiguration.class }) +@EnableConfigurationProperties({ LiquibaseProperties.class, ProgrammingLanguageConfiguration.class, TheiaConfiguration.class, LicenseConfiguration.class }) public class ArtemisApp { private static final Logger log = LoggerFactory.getLogger(ArtemisApp.class); diff --git a/src/main/java/de/tum/cit/aet/artemis/buildagent/service/BuildJobContainerService.java b/src/main/java/de/tum/cit/aet/artemis/buildagent/service/BuildJobContainerService.java index 891de1f8af4c..89c137972c66 100644 --- a/src/main/java/de/tum/cit/aet/artemis/buildagent/service/BuildJobContainerService.java +++ b/src/main/java/de/tum/cit/aet/artemis/buildagent/service/BuildJobContainerService.java @@ -108,7 +108,7 @@ public CreateContainerResponse configureContainer(String containerName, String i // container from exiting until it finishes. // It waits until the script that is running the tests (see below execCreateCmdResponse) is completed, and until the result files are extracted which is indicated // by the creation of a file "stop_container.txt" in the container's root directory. - .withCmd("sh", "-c", "while [ ! -f " + LOCALCI_WORKING_DIRECTORY + "/stop_container.txt ]; do sleep 0.5; done") + .withEntrypoint().withCmd("sh", "-c", "while [ ! -f " + LOCALCI_WORKING_DIRECTORY + "/stop_container.txt ]; do sleep 0.5; done") // .withCmd("tail", "-f", "/dev/null") // Activate for debugging purposes instead of the above command to get a running container that you can peek into using // "docker exec -it /bin/bash". .exec(); diff --git a/src/main/java/de/tum/cit/aet/artemis/core/config/LicenseConfiguration.java b/src/main/java/de/tum/cit/aet/artemis/core/config/LicenseConfiguration.java new file mode 100644 index 000000000000..6ebe6709d2d1 --- /dev/null +++ b/src/main/java/de/tum/cit/aet/artemis/core/config/LicenseConfiguration.java @@ -0,0 +1,30 @@ +package de.tum.cit.aet.artemis.core.config; + +import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE; + +import jakarta.annotation.Nullable; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Profile; + +@Profile(PROFILE_CORE) +@ConfigurationProperties(prefix = "artemis.licenses") +public class LicenseConfiguration { + + private final MatLabLicense matlab; + + public record MatLabLicense(String licenseServer) { + } + + public LicenseConfiguration(MatLabLicense matlab) { + this.matlab = matlab; + } + + @Nullable + public String getMatlabLicenseServer() { + if (matlab == null) { + return null; + } + return matlab.licenseServer(); + } +} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/domain/ProgrammingLanguage.java b/src/main/java/de/tum/cit/aet/artemis/programming/domain/ProgrammingLanguage.java index 0c4894182e1e..5f4a1451720a 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/domain/ProgrammingLanguage.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/domain/ProgrammingLanguage.java @@ -48,9 +48,11 @@ public enum ProgrammingLanguage { JAVA, JAVASCRIPT, KOTLIN, + MATLAB, OCAML, PYTHON, R, + RUBY, RUST, SWIFT, TYPESCRIPT, diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/LicenseService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/LicenseService.java new file mode 100644 index 000000000000..4999dccc4d30 --- /dev/null +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/LicenseService.java @@ -0,0 +1,60 @@ +package de.tum.cit.aet.artemis.programming.service; + +import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE; + +import java.util.Map; +import java.util.Objects; + +import jakarta.annotation.Nullable; + +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Service; + +import de.tum.cit.aet.artemis.core.config.LicenseConfiguration; +import de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage; +import de.tum.cit.aet.artemis.programming.domain.ProjectType; + +/** + * Provides licensing information for proprietary software to build jobs via environment variables. + */ +@Profile(PROFILE_CORE) +@Service +public class LicenseService { + + private final LicenseConfiguration licenseConfiguration; + + public LicenseService(LicenseConfiguration licenseConfiguration) { + this.licenseConfiguration = licenseConfiguration; + } + + /** + * Checks whether a required license is configured for the specified exercise type. + * If no license is required this returns true. + * + * @param programmingLanguage the programming language of the exercise type + * @param projectType the project type of the exercise type + * @return whether a required license is configured + */ + public boolean isLicensed(ProgrammingLanguage programmingLanguage, @Nullable ProjectType projectType) { + if (programmingLanguage == ProgrammingLanguage.MATLAB && projectType == null) { + return licenseConfiguration.getMatlabLicenseServer() != null; + } + + return true; + } + + /** + * Returns environment variables required to run programming exercise tests. + * + * @param programmingLanguage the programming language of the exercise + * @param projectType the project type of the exercise + * @return environment variables for the specified exercise type + */ + public Map getEnvironment(ProgrammingLanguage programmingLanguage, @Nullable ProjectType projectType) { + if (programmingLanguage == ProgrammingLanguage.MATLAB && projectType == null) { + return Map.of("MLM_LICENSE_FILE", Objects.requireNonNull(licenseConfiguration.getMatlabLicenseServer())); + } + + return Map.of(); + } +} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseBuildConfigService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseBuildConfigService.java index 5ccf7f2045a6..bc8a6a93dc4b 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseBuildConfigService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseBuildConfigService.java @@ -3,6 +3,7 @@ import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -17,7 +18,10 @@ import de.tum.cit.aet.artemis.buildagent.dto.DockerFlagsDTO; import de.tum.cit.aet.artemis.buildagent.dto.DockerRunConfig; +import de.tum.cit.aet.artemis.programming.domain.ProgrammingExercise; import de.tum.cit.aet.artemis.programming.domain.ProgrammingExerciseBuildConfig; +import de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage; +import de.tum.cit.aet.artemis.programming.domain.ProjectType; @Profile(PROFILE_CORE) @Service @@ -27,6 +31,12 @@ public class ProgrammingExerciseBuildConfigService { private final ObjectMapper objectMapper = new ObjectMapper(); + private final LicenseService licenseService; + + public ProgrammingExerciseBuildConfigService(LicenseService licenseService) { + this.licenseService = licenseService; + } + /** * Converts a JSON string representing Docker flags (in JSON format) * into a {@link DockerRunConfig} instance. @@ -46,25 +56,60 @@ public class ProgrammingExerciseBuildConfigService { public DockerRunConfig getDockerRunConfig(ProgrammingExerciseBuildConfig buildConfig) { DockerFlagsDTO dockerFlagsDTO = parseDockerFlags(buildConfig); - return getDockerRunConfigFromParsedFlags(dockerFlagsDTO); + String network; + Map exerciseEnvironment; + if (dockerFlagsDTO != null) { + network = dockerFlagsDTO.network(); + exerciseEnvironment = dockerFlagsDTO.env(); + } + else { + network = null; + exerciseEnvironment = null; + } + + ProgrammingExercise exercise = buildConfig.getProgrammingExercise(); + if (exercise == null) { + return createDockerRunConfig(network, exerciseEnvironment); + } + + ProgrammingLanguage programmingLanguage = exercise.getProgrammingLanguage(); + ProjectType projectType = exercise.getProjectType(); + Map environment = addLanguageSpecificEnvironment(exerciseEnvironment, programmingLanguage, projectType); + + return createDockerRunConfig(network, environment); + } + + @Nullable + private Map addLanguageSpecificEnvironment(@Nullable Map exerciseEnvironment, ProgrammingLanguage language, ProjectType projectType) { + Map licenseEnvironment = licenseService.getEnvironment(language, projectType); + if (licenseEnvironment.isEmpty()) { + return exerciseEnvironment; + } + + Map env = new HashMap<>(licenseEnvironment); + if (exerciseEnvironment != null) { + env.putAll(exerciseEnvironment); + } + + return env; } - DockerRunConfig getDockerRunConfigFromParsedFlags(DockerFlagsDTO dockerFlagsDTO) { - if (dockerFlagsDTO == null) { + DockerRunConfig createDockerRunConfig(String network, Map environmentMap) { + if (network == null && environmentMap == null) { return null; } - List env = new ArrayList<>(); - boolean isNetworkDisabled = dockerFlagsDTO.network() != null && dockerFlagsDTO.network().equals("none"); + List environmentStrings = new ArrayList<>(); + boolean isNetworkDisabled = network != null && network.equals("none"); - if (dockerFlagsDTO.env() != null) { - for (Map.Entry entry : dockerFlagsDTO.env().entrySet()) { + if (environmentMap != null) { + for (Map.Entry entry : environmentMap.entrySet()) { String key = entry.getKey(); String value = entry.getValue(); - env.add(key + "=" + value); + environmentStrings.add(key + "=" + value); } } - return new DockerRunConfig(isNetworkDisabled, env); + return new DockerRunConfig(isNetworkDisabled, environmentStrings); } /** diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseService.java index 886fa2622c8d..a5d448daa272 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseService.java @@ -1102,7 +1102,7 @@ public void validateDockerFlags(ProgrammingExercise programmingExercise) { } } - DockerRunConfig dockerRunConfig = programmingExerciseBuildConfigService.getDockerRunConfigFromParsedFlags(dockerFlagsDTO); + DockerRunConfig dockerRunConfig = programmingExerciseBuildConfigService.createDockerRunConfig(dockerFlagsDTO.network(), dockerFlagsDTO.env()); if (List.of(ProgrammingLanguage.SWIFT, ProgrammingLanguage.HASKELL).contains(programmingExercise.getProgrammingLanguage()) && dockerRunConfig.isNetworkDisabled()) { throw new BadRequestAlertException("This programming language does not support disabling the network access feature", "Exercise", "networkAccessNotSupported"); diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingLanguageFeatureService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingLanguageFeatureService.java index e6049a4221d2..22f29fa2b041 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingLanguageFeatureService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingLanguageFeatureService.java @@ -1,14 +1,17 @@ package de.tum.cit.aet.artemis.programming.service; -import java.util.HashMap; +import java.util.EnumMap; import java.util.Map; +import jakarta.annotation.Nullable; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.actuate.info.Info; import org.springframework.boot.actuate.info.InfoContributor; import de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage; +import de.tum.cit.aet.artemis.programming.domain.ProjectType; /** * This service provides information about features the different ProgrammingLanguages support. @@ -18,7 +21,16 @@ public abstract class ProgrammingLanguageFeatureService implements InfoContribut private static final Logger log = LoggerFactory.getLogger(ProgrammingLanguageFeatureService.class); - protected final Map programmingLanguageFeatures = new HashMap<>(); + private final LicenseService licenseService; + + private final Map programmingLanguageFeatures; + + protected ProgrammingLanguageFeatureService(LicenseService licenseService) { + this.licenseService = licenseService; + this.programmingLanguageFeatures = getEnabledFeatures(); + } + + protected abstract Map getSupportedProgrammingLanguageFeatures(); /** * Get the ProgrammingLanguageFeature configured for the given ProgrammingLanguage. @@ -37,12 +49,44 @@ public ProgrammingLanguageFeature getProgrammingLanguageFeatures(ProgrammingLang return programmingLanguageFeature; } - public Map getProgrammingLanguageFeatures() { - return programmingLanguageFeatures; - } - @Override public void contribute(Info.Builder builder) { - builder.withDetail("programmingLanguageFeatures", getProgrammingLanguageFeatures().values()); + builder.withDetail("programmingLanguageFeatures", programmingLanguageFeatures.values()); + } + + private Map getEnabledFeatures() { + var features = new EnumMap(ProgrammingLanguage.class); + for (var programmingLanguageFeatureEntry : getSupportedProgrammingLanguageFeatures().entrySet()) { + var language = programmingLanguageFeatureEntry.getKey(); + var feature = programmingLanguageFeatureEntry.getValue(); + if (feature.projectTypes().isEmpty()) { + if (isProjectTypeUsable(language, null)) { + features.put(language, feature); + } + } + else { + var filteredProjectTypes = feature.projectTypes().stream().filter((projectType -> isProjectTypeUsable(language, projectType))).toList(); + if (!filteredProjectTypes.isEmpty()) { + // @formatter:off + var filteredFeature = new ProgrammingLanguageFeature( + feature.programmingLanguage(), + feature.sequentialTestRuns(), + feature.staticCodeAnalysis(), + feature.plagiarismCheckSupported(), + feature.packageNameRequired(), + feature.checkoutSolutionRepositoryAllowed(), + filteredProjectTypes, + feature.auxiliaryRepositoriesSupported() + ); + // @formatter:on + features.put(language, filteredFeature); + } + } + } + return features; + } + + private boolean isProjectTypeUsable(ProgrammingLanguage programmingLanguage, @Nullable ProjectType projectType) { + return licenseService.isLicensed(programmingLanguage, projectType); } } diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/TemplateUpgradePolicyService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/TemplateUpgradePolicyService.java index 9dd3249bd1a4..baad5929c94e 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/TemplateUpgradePolicyService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/TemplateUpgradePolicyService.java @@ -32,9 +32,9 @@ public TemplateUpgradePolicyService(JavaTemplateUpgradeService javaRepositoryUpg public TemplateUpgradeService getUpgradeService(ProgrammingLanguage programmingLanguage) { return switch (programmingLanguage) { case JAVA -> javaRepositoryUpgradeService; - case KOTLIN, PYTHON, C, HASKELL, VHDL, ASSEMBLER, SWIFT, OCAML, EMPTY, RUST, JAVASCRIPT, R, C_PLUS_PLUS, TYPESCRIPT, C_SHARP, GO, BASH -> + case KOTLIN, PYTHON, C, HASKELL, VHDL, ASSEMBLER, SWIFT, OCAML, EMPTY, RUST, JAVASCRIPT, R, C_PLUS_PLUS, TYPESCRIPT, C_SHARP, GO, BASH, MATLAB, RUBY -> defaultRepositoryUpgradeService; - case SQL, MATLAB, RUBY, POWERSHELL, ADA, DART, PHP -> throw new UnsupportedOperationException("Unsupported programming language: " + programmingLanguage); + case SQL, POWERSHELL, ADA, DART, PHP -> throw new UnsupportedOperationException("Unsupported programming language: " + programmingLanguage); }; } } diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/ci/ContinuousIntegrationService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/ci/ContinuousIntegrationService.java index 3f14c1b2a015..01ff763c0154 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/ci/ContinuousIntegrationService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/ci/ContinuousIntegrationService.java @@ -219,8 +219,9 @@ enum RepositoryCheckoutPath implements CustomizableCheckoutPath { @Override public String forProgrammingLanguage(ProgrammingLanguage language) { return switch (language) { - case JAVA, PYTHON, C, HASKELL, KOTLIN, VHDL, ASSEMBLER, SWIFT, OCAML, EMPTY, RUST, JAVASCRIPT, R, C_PLUS_PLUS, TYPESCRIPT, C_SHARP, GO, BASH -> "assignment"; - case SQL, MATLAB, RUBY, POWERSHELL, ADA, DART, PHP -> throw new UnsupportedOperationException("Unsupported programming language: " + language); + case JAVA, PYTHON, C, HASKELL, KOTLIN, VHDL, ASSEMBLER, SWIFT, OCAML, EMPTY, RUST, JAVASCRIPT, R, C_PLUS_PLUS, TYPESCRIPT, C_SHARP, GO, BASH, MATLAB, RUBY -> + "assignment"; + case SQL, POWERSHELL, ADA, DART, PHP -> throw new UnsupportedOperationException("Unsupported programming language: " + language); }; } }, @@ -230,8 +231,8 @@ public String forProgrammingLanguage(ProgrammingLanguage language) { public String forProgrammingLanguage(ProgrammingLanguage language) { return switch (language) { case JAVA, PYTHON, HASKELL, KOTLIN, SWIFT, EMPTY, RUST, JAVASCRIPT, R, C_PLUS_PLUS, TYPESCRIPT -> ""; - case C, VHDL, ASSEMBLER, OCAML, C_SHARP, GO, BASH -> "tests"; - case SQL, MATLAB, RUBY, POWERSHELL, ADA, DART, PHP -> throw new UnsupportedOperationException("Unsupported programming language: " + language); + case C, VHDL, ASSEMBLER, OCAML, C_SHARP, GO, BASH, MATLAB, RUBY -> "tests"; + case SQL, POWERSHELL, ADA, DART, PHP -> throw new UnsupportedOperationException("Unsupported programming language: " + language); }; } }, diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/gitlabci/GitLabCIProgrammingLanguageFeatureService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/gitlabci/GitLabCIProgrammingLanguageFeatureService.java index 357cf05d97f8..7035d1235ccb 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/gitlabci/GitLabCIProgrammingLanguageFeatureService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/gitlabci/GitLabCIProgrammingLanguageFeatureService.java @@ -7,11 +7,15 @@ import static de.tum.cit.aet.artemis.programming.domain.ProjectType.MAVEN_MAVEN; import static de.tum.cit.aet.artemis.programming.domain.ProjectType.PLAIN_MAVEN; +import java.util.EnumMap; import java.util.List; +import java.util.Map; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Service; +import de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage; +import de.tum.cit.aet.artemis.programming.service.LicenseService; import de.tum.cit.aet.artemis.programming.service.ProgrammingLanguageFeature; import de.tum.cit.aet.artemis.programming.service.ProgrammingLanguageFeatureService; @@ -22,10 +26,17 @@ @Profile("gitlabci") public class GitLabCIProgrammingLanguageFeatureService extends ProgrammingLanguageFeatureService { - public GitLabCIProgrammingLanguageFeatureService() { + protected GitLabCIProgrammingLanguageFeatureService(LicenseService licenseService) { + super(licenseService); + } + + @Override + protected Map getSupportedProgrammingLanguageFeatures() { + EnumMap programmingLanguageFeatures = new EnumMap<>(ProgrammingLanguage.class); programmingLanguageFeatures.put(EMPTY, new ProgrammingLanguageFeature(EMPTY, false, false, false, false, false, List.of(), false)); programmingLanguageFeatures.put(JAVA, new ProgrammingLanguageFeature(JAVA, false, false, false, true, false, List.of(PLAIN_MAVEN, MAVEN_MAVEN), false)); programmingLanguageFeatures.put(JAVASCRIPT, new ProgrammingLanguageFeature(JAVASCRIPT, false, false, true, false, false, List.of(), false)); programmingLanguageFeatures.put(RUST, new ProgrammingLanguageFeature(RUST, false, false, true, false, false, List.of(), false)); + return programmingLanguageFeatures; } } diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsProgrammingLanguageFeatureService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsProgrammingLanguageFeatureService.java index ec4a7dd3598a..74cd0bd1e183 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsProgrammingLanguageFeatureService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsProgrammingLanguageFeatureService.java @@ -13,6 +13,7 @@ import static de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage.KOTLIN; import static de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage.PYTHON; import static de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage.R; +import static de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage.RUBY; import static de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage.RUST; import static de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage.SWIFT; import static de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage.TYPESCRIPT; @@ -25,11 +26,15 @@ import static de.tum.cit.aet.artemis.programming.domain.ProjectType.PLAIN_GRADLE; import static de.tum.cit.aet.artemis.programming.domain.ProjectType.PLAIN_MAVEN; +import java.util.EnumMap; import java.util.List; +import java.util.Map; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Service; +import de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage; +import de.tum.cit.aet.artemis.programming.service.LicenseService; import de.tum.cit.aet.artemis.programming.service.ProgrammingLanguageFeature; import de.tum.cit.aet.artemis.programming.service.ProgrammingLanguageFeatureService; @@ -37,8 +42,14 @@ @Profile(PROFILE_JENKINS) public class JenkinsProgrammingLanguageFeatureService extends ProgrammingLanguageFeatureService { - public JenkinsProgrammingLanguageFeatureService() { + protected JenkinsProgrammingLanguageFeatureService(LicenseService licenseService) { + super(licenseService); + } + + @Override + protected Map getSupportedProgrammingLanguageFeatures() { // Must be extended once a new programming language is added + EnumMap programmingLanguageFeatures = new EnumMap<>(ProgrammingLanguage.class); programmingLanguageFeatures.put(EMPTY, new ProgrammingLanguageFeature(EMPTY, false, false, false, false, false, List.of(), false)); programmingLanguageFeatures.put(BASH, new ProgrammingLanguageFeature(BASH, false, false, false, false, false, List.of(), false)); programmingLanguageFeatures.put(C, new ProgrammingLanguageFeature(C, false, false, true, false, false, List.of(FACT, GCC), false)); @@ -52,9 +63,11 @@ public JenkinsProgrammingLanguageFeatureService() { programmingLanguageFeatures.put(KOTLIN, new ProgrammingLanguageFeature(KOTLIN, true, false, true, true, false, List.of(), false)); programmingLanguageFeatures.put(PYTHON, new ProgrammingLanguageFeature(PYTHON, false, false, true, false, false, List.of(), false)); programmingLanguageFeatures.put(R, new ProgrammingLanguageFeature(R, false, false, true, false, false, List.of(), false)); + programmingLanguageFeatures.put(RUBY, new ProgrammingLanguageFeature(RUBY, false, false, false, false, false, List.of(), false)); programmingLanguageFeatures.put(RUST, new ProgrammingLanguageFeature(RUST, false, false, true, false, false, List.of(), false)); // Jenkins is not supporting XCODE at the moment programmingLanguageFeatures.put(SWIFT, new ProgrammingLanguageFeature(SWIFT, false, true, true, true, false, List.of(PLAIN), false)); programmingLanguageFeatures.put(TYPESCRIPT, new ProgrammingLanguageFeature(TYPESCRIPT, false, false, true, false, false, List.of(), false)); + return programmingLanguageFeatures; } } diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/build_plan/JenkinsBuildPlanService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/build_plan/JenkinsBuildPlanService.java index 970ffb719c51..b639d6ae34d2 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/build_plan/JenkinsBuildPlanService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/build_plan/JenkinsBuildPlanService.java @@ -172,8 +172,8 @@ private JenkinsXmlConfigBuilder builderFor(ProgrammingLanguage programmingLangua throw new UnsupportedOperationException("Xcode templates are not available for Jenkins."); } return switch (programmingLanguage) { - case JAVA, KOTLIN, PYTHON, C, HASKELL, SWIFT, EMPTY, RUST, JAVASCRIPT, R, C_PLUS_PLUS, TYPESCRIPT, C_SHARP, GO, BASH -> jenkinsBuildPlanCreator; - case VHDL, ASSEMBLER, OCAML, SQL, MATLAB, RUBY, POWERSHELL, ADA, DART, PHP -> + case JAVA, KOTLIN, PYTHON, C, HASKELL, SWIFT, EMPTY, RUST, JAVASCRIPT, R, C_PLUS_PLUS, TYPESCRIPT, C_SHARP, GO, BASH, RUBY -> jenkinsBuildPlanCreator; + case VHDL, ASSEMBLER, OCAML, SQL, MATLAB, POWERSHELL, ADA, DART, PHP -> throw new UnsupportedOperationException(programmingLanguage + " templates are not available for Jenkins."); }; } diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/LocalCIProgrammingLanguageFeatureService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/LocalCIProgrammingLanguageFeatureService.java index e9c7e13b8659..3a10c46e8f9a 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/LocalCIProgrammingLanguageFeatureService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/LocalCIProgrammingLanguageFeatureService.java @@ -12,9 +12,11 @@ import static de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage.JAVA; import static de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage.JAVASCRIPT; import static de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage.KOTLIN; +import static de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage.MATLAB; import static de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage.OCAML; import static de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage.PYTHON; import static de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage.R; +import static de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage.RUBY; import static de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage.RUST; import static de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage.SWIFT; import static de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage.TYPESCRIPT; @@ -27,11 +29,15 @@ import static de.tum.cit.aet.artemis.programming.domain.ProjectType.PLAIN_GRADLE; import static de.tum.cit.aet.artemis.programming.domain.ProjectType.PLAIN_MAVEN; +import java.util.EnumMap; import java.util.List; +import java.util.Map; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Service; +import de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage; +import de.tum.cit.aet.artemis.programming.service.LicenseService; import de.tum.cit.aet.artemis.programming.service.ProgrammingLanguageFeature; import de.tum.cit.aet.artemis.programming.service.ProgrammingLanguageFeatureService; @@ -42,8 +48,14 @@ @Profile(PROFILE_LOCALCI) public class LocalCIProgrammingLanguageFeatureService extends ProgrammingLanguageFeatureService { - public LocalCIProgrammingLanguageFeatureService() { + protected LocalCIProgrammingLanguageFeatureService(LicenseService licenseService) { + super(licenseService); + } + + @Override + protected Map getSupportedProgrammingLanguageFeatures() { // Must be extended once a new programming language is added + EnumMap programmingLanguageFeatures = new EnumMap<>(ProgrammingLanguage.class); programmingLanguageFeatures.put(EMPTY, new ProgrammingLanguageFeature(EMPTY, false, false, false, false, false, List.of(), true)); programmingLanguageFeatures.put(ASSEMBLER, new ProgrammingLanguageFeature(ASSEMBLER, false, false, false, false, false, List.of(), true)); programmingLanguageFeatures.put(BASH, new ProgrammingLanguageFeature(BASH, false, false, false, false, false, List.of(), true)); @@ -56,12 +68,15 @@ public LocalCIProgrammingLanguageFeatureService() { new ProgrammingLanguageFeature(JAVA, true, true, true, true, false, List.of(PLAIN_GRADLE, GRADLE_GRADLE, PLAIN_MAVEN, MAVEN_MAVEN), true)); programmingLanguageFeatures.put(JAVASCRIPT, new ProgrammingLanguageFeature(JAVASCRIPT, false, false, true, false, false, List.of(), true)); programmingLanguageFeatures.put(KOTLIN, new ProgrammingLanguageFeature(KOTLIN, false, false, true, true, false, List.of(), true)); + programmingLanguageFeatures.put(MATLAB, new ProgrammingLanguageFeature(MATLAB, false, false, false, false, false, List.of(), true)); programmingLanguageFeatures.put(OCAML, new ProgrammingLanguageFeature(OCAML, false, false, false, false, true, List.of(), true)); programmingLanguageFeatures.put(PYTHON, new ProgrammingLanguageFeature(PYTHON, false, true, true, false, false, List.of(), true)); programmingLanguageFeatures.put(R, new ProgrammingLanguageFeature(R, false, false, true, false, false, List.of(), true)); + programmingLanguageFeatures.put(RUBY, new ProgrammingLanguageFeature(RUBY, false, false, false, false, false, List.of(), true)); programmingLanguageFeatures.put(RUST, new ProgrammingLanguageFeature(RUST, false, false, true, false, false, List.of(), true)); programmingLanguageFeatures.put(SWIFT, new ProgrammingLanguageFeature(SWIFT, false, false, true, true, false, List.of(PLAIN), true)); programmingLanguageFeatures.put(TYPESCRIPT, new ProgrammingLanguageFeature(TYPESCRIPT, false, false, true, false, false, List.of(), true)); programmingLanguageFeatures.put(VHDL, new ProgrammingLanguageFeature(VHDL, false, false, false, false, false, List.of(), true)); + return programmingLanguageFeatures; } } diff --git a/src/main/resources/config/application.yml b/src/main/resources/config/application.yml index e9aebc1980b1..04634254d4ac 100644 --- a/src/main/resources/config/application.yml +++ b/src/main/resources/config/application.yml @@ -105,6 +105,10 @@ artemis: default: "ghcr.io/ls1intum/artemis-javascript-docker:v1.0.0" go: default: "ghcr.io/ls1intum/artemis-go-docker:v1.0.0" + matlab: + default: "mathworks/matlab:r2024b" + ruby: + default: "ghcr.io/ls1intum/artemis-ruby-docker:v1.0.0" # The following properties are used to configure the Artemis build agent. # The build agent is responsible for executing the buildJob to test student submissions. diff --git a/src/main/resources/templates/aeolus/matlab/default.sh b/src/main/resources/templates/aeolus/matlab/default.sh new file mode 100644 index 000000000000..1c44cf9a481a --- /dev/null +++ b/src/main/resources/templates/aeolus/matlab/default.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +set -e + +test () { + echo '⚙️ executing test' + cd "${testWorkingDirectory}" + + sudo mkdir test-results + sudo chown matlab:matlab test-results + sudo rm /etc/sudoers.d/matlab + + matlab -batch testRunner + +} + +main () { + test +} + +main "${@}" diff --git a/src/main/resources/templates/aeolus/matlab/default.yaml b/src/main/resources/templates/aeolus/matlab/default.yaml new file mode 100644 index 000000000000..e2a61f4b62f2 --- /dev/null +++ b/src/main/resources/templates/aeolus/matlab/default.yaml @@ -0,0 +1,18 @@ +api: v0.0.1 +metadata: + name: "MATLAB" + id: matlab +actions: + - name: test + script: | + cd "${testWorkingDirectory}" + + sudo mkdir test-results + sudo chown matlab:matlab test-results + sudo rm /etc/sudoers.d/matlab + + matlab -batch testRunner + results: + - name: Test Results + path: "${testWorkingDirectory}/test-results/results.xml" + type: junit diff --git a/src/main/resources/templates/aeolus/ruby/default.sh b/src/main/resources/templates/aeolus/ruby/default.sh new file mode 100644 index 000000000000..eca5b3e5156c --- /dev/null +++ b/src/main/resources/templates/aeolus/ruby/default.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +set -e + +test () { + echo '⚙️ executing test' + cd "${testWorkingDirectory}" + bundler exec rake ci:test +} + +main () { + test +} + +main "${@}" diff --git a/src/main/resources/templates/aeolus/ruby/default.yaml b/src/main/resources/templates/aeolus/ruby/default.yaml new file mode 100644 index 000000000000..bd32a925c8e4 --- /dev/null +++ b/src/main/resources/templates/aeolus/ruby/default.yaml @@ -0,0 +1,13 @@ +api: v0.0.1 +metadata: + name: "Ruby" + id: ruby +actions: + - name: test + script: |- + cd "${testWorkingDirectory}" + bundler exec rake ci:test + results: + - name: Minitest Test Results + path: "${testWorkingDirectory}/report.xml" + type: junit diff --git a/src/main/resources/templates/jenkins/ruby/regularRuns/pipeline.groovy b/src/main/resources/templates/jenkins/ruby/regularRuns/pipeline.groovy new file mode 100644 index 000000000000..b3afb23cdf64 --- /dev/null +++ b/src/main/resources/templates/jenkins/ruby/regularRuns/pipeline.groovy @@ -0,0 +1,55 @@ +/* + * This file configures the actual build steps for the automatic grading. + * + * !!! + * For regular exercises, there is no need to make changes to this file. + * Only this base configuration is actively supported by the Artemis maintainers + * and/or your Artemis instance administrators. + * !!! + */ + +dockerImage = '#dockerImage' +dockerFlags = '#dockerArgs' + +/** + * Main function called by Jenkins. + */ +void testRunner() { + docker.image(dockerImage).inside(dockerFlags) { c -> + runTestSteps() + } +} + +private void runTestSteps() { + test() +} + +/** + * Run unit tests + */ +private void test() { + stage('Test') { + sh ''' + cd tests + bundler exec rake ci:test + ''' + } +} + +/** + * Script of the post build tasks aggregating all JUnit files in $WORKSPACE/results. + * + * Called by Jenkins. + */ +void postBuildTasks() { + sh ''' + rm -rf results + mkdir results + cp tests/report.xml $WORKSPACE/results/ || true + sed -i 's/[^[:print:]\t]/�/g' $WORKSPACE/results/*.xml || true + ''' +} + +// very important, do not remove +// required so that Jenkins finds the methods defined in this script +return this diff --git a/src/main/resources/templates/matlab/exercise/.gitattributes b/src/main/resources/templates/matlab/exercise/.gitattributes new file mode 100644 index 000000000000..7871ff363b73 --- /dev/null +++ b/src/main/resources/templates/matlab/exercise/.gitattributes @@ -0,0 +1,28 @@ +* text=auto + +*.fig binary +*.mat binary +*.mdl binary diff merge=mlAutoMerge +*.mdlp binary +*.mex* binary +*.mlapp binary +*.mldatx binary +*.mlproj binary +*.mlx binary +*.p binary +*.sfx binary +*.sldd binary +*.slreqx binary merge=mlAutoMerge +*.slmx binary merge=mlAutoMerge +*.sltx binary +*.slxc binary +*.slx binary merge=mlAutoMerge +*.slxp binary + +## Other common binary file types +*.docx binary +*.exe binary +*.jpg binary +*.pdf binary +*.png binary +*.xlsx binary diff --git a/src/main/resources/templates/matlab/exercise/.gitignore b/src/main/resources/templates/matlab/exercise/.gitignore new file mode 100644 index 000000000000..ce3b7fbfc90e --- /dev/null +++ b/src/main/resources/templates/matlab/exercise/.gitignore @@ -0,0 +1,36 @@ +# Autosave files +*.asv +*.m~ +*.autosave +*.slx.r* +*.mdl.r* + +# Derived content-obscured files +*.p + +# Compiled MEX files +*.mex* + +# Packaged app and toolbox files +*.mlappinstall +*.mltbx + +# Deployable archives +*.ctf + +# Generated helpsearch folders +helpsearch*/ + +# Code generation folders +slprj/ +sccprj/ +codegen/ + +# Cache files +*.slxc + +# Cloud based storage dotfile +.MATLABDriveTag + +# buildtool cache folder +.buildtool/ diff --git a/src/main/resources/templates/matlab/exercise/averageGradeByStudent.m b/src/main/resources/templates/matlab/exercise/averageGradeByStudent.m new file mode 100644 index 000000000000..847a4cbb6fd9 --- /dev/null +++ b/src/main/resources/templates/matlab/exercise/averageGradeByStudent.m @@ -0,0 +1,3 @@ +function avg = averageGradeByStudent(grades) + % TODO: Task 2 +end diff --git a/src/main/resources/templates/matlab/exercise/finalGrade.m b/src/main/resources/templates/matlab/exercise/finalGrade.m new file mode 100644 index 000000000000..863131c288d8 --- /dev/null +++ b/src/main/resources/templates/matlab/exercise/finalGrade.m @@ -0,0 +1,3 @@ +function g = finalGrade(grades,weights) + % TODO: Task 3 +end diff --git a/src/main/resources/templates/matlab/exercise/medianGradeByAssignment.m b/src/main/resources/templates/matlab/exercise/medianGradeByAssignment.m new file mode 100644 index 000000000000..7ebeb2e39b3a --- /dev/null +++ b/src/main/resources/templates/matlab/exercise/medianGradeByAssignment.m @@ -0,0 +1,3 @@ +function avg = medianGradeByAssignment(grades) + % TODO: Task 1 +end diff --git a/src/main/resources/templates/matlab/readme b/src/main/resources/templates/matlab/readme new file mode 100644 index 000000000000..0a5ee1f0f265 --- /dev/null +++ b/src/main/resources/templates/matlab/readme @@ -0,0 +1,49 @@ +# Grading Statistics + +In this exercise, you will analyze student performance based on a matrix of +grades. Each row in the matrix represents a student, and each column represents +an assignment. + +Your task is to implement three functions that compute statistical measures for +this data. Each function has a specific purpose, described below: + +## Task 1: Median Grade by Assignment +Compute the median grade for each assignment across all students. The result +should be a row vector where each element corresponds to the median of a +specific assignment (column). + +- **Function to Implement**: `medianGradeByAssignment()` +- **Input**: A matrix `grades` where rows are students and columns are assignments. +- **Output**: A row vector of medians, one for each assignment. + +[task][Median Grade by Assignment](testMedianGradeByAssignment) + +--- + +## Task 2: Average Grade by Student +Calculate the arithmetic mean grade for each student across all their assignments. +The result should be a row vector where each element corresponds to a student's +arithmetic mean grade. + +- **Function to Implement**: `averageGradeByStudent()` +- **Input**: A matrix `grades` where rows are students and columns are assignments. +- **Output**: A row vector of averages, one for each student. + +[task][Average Grade by Student](testAverageGradeByStudent) + +--- + +## Task 3: Final Grade +Determine the weighted final grade for each student. In addition to the +`grades` matrix, you are provided with a row vector `weights` (summing to 1) +that represents the weight of each assignment. Compute the weighted average for +each student and round the result to 1 decimal place. The result should be a +row vector where each element corresponds to a student’s final grade. + +- **Function to Implement**: `finalGrade()` +- **Input**: + 1. A matrix `grades` where rows are students and columns are assignments. + 2. A row vector `weights` with the same number of elements as columns in the `grades` matrix. +- **Output**: A row vector of weighted final grades, rounded to 1 decimal place. + +[task][Final Grade](testFinalGrade) diff --git a/src/main/resources/templates/matlab/solution/.gitattributes b/src/main/resources/templates/matlab/solution/.gitattributes new file mode 100644 index 000000000000..7871ff363b73 --- /dev/null +++ b/src/main/resources/templates/matlab/solution/.gitattributes @@ -0,0 +1,28 @@ +* text=auto + +*.fig binary +*.mat binary +*.mdl binary diff merge=mlAutoMerge +*.mdlp binary +*.mex* binary +*.mlapp binary +*.mldatx binary +*.mlproj binary +*.mlx binary +*.p binary +*.sfx binary +*.sldd binary +*.slreqx binary merge=mlAutoMerge +*.slmx binary merge=mlAutoMerge +*.sltx binary +*.slxc binary +*.slx binary merge=mlAutoMerge +*.slxp binary + +## Other common binary file types +*.docx binary +*.exe binary +*.jpg binary +*.pdf binary +*.png binary +*.xlsx binary diff --git a/src/main/resources/templates/matlab/solution/.gitignore b/src/main/resources/templates/matlab/solution/.gitignore new file mode 100644 index 000000000000..ce3b7fbfc90e --- /dev/null +++ b/src/main/resources/templates/matlab/solution/.gitignore @@ -0,0 +1,36 @@ +# Autosave files +*.asv +*.m~ +*.autosave +*.slx.r* +*.mdl.r* + +# Derived content-obscured files +*.p + +# Compiled MEX files +*.mex* + +# Packaged app and toolbox files +*.mlappinstall +*.mltbx + +# Deployable archives +*.ctf + +# Generated helpsearch folders +helpsearch*/ + +# Code generation folders +slprj/ +sccprj/ +codegen/ + +# Cache files +*.slxc + +# Cloud based storage dotfile +.MATLABDriveTag + +# buildtool cache folder +.buildtool/ diff --git a/src/main/resources/templates/matlab/solution/averageGradeByStudent.m b/src/main/resources/templates/matlab/solution/averageGradeByStudent.m new file mode 100644 index 000000000000..6bc13004967a --- /dev/null +++ b/src/main/resources/templates/matlab/solution/averageGradeByStudent.m @@ -0,0 +1,3 @@ +function avg = averageGradeByStudent(grades) + avg = mean(grades,2).'; +end diff --git a/src/main/resources/templates/matlab/solution/finalGrade.m b/src/main/resources/templates/matlab/solution/finalGrade.m new file mode 100644 index 000000000000..4fdfe1c2b8bb --- /dev/null +++ b/src/main/resources/templates/matlab/solution/finalGrade.m @@ -0,0 +1,3 @@ +function g = finalGrade(grades,weights) + g = round((grades * weights.').',1); +end diff --git a/src/main/resources/templates/matlab/solution/medianGradeByAssignment.m b/src/main/resources/templates/matlab/solution/medianGradeByAssignment.m new file mode 100644 index 000000000000..31d5fe2b5800 --- /dev/null +++ b/src/main/resources/templates/matlab/solution/medianGradeByAssignment.m @@ -0,0 +1,3 @@ +function avg = medianGradeByAssignment(grades) + avg = median(grades); +end diff --git a/src/main/resources/templates/matlab/test/.gitattributes b/src/main/resources/templates/matlab/test/.gitattributes new file mode 100644 index 000000000000..7871ff363b73 --- /dev/null +++ b/src/main/resources/templates/matlab/test/.gitattributes @@ -0,0 +1,28 @@ +* text=auto + +*.fig binary +*.mat binary +*.mdl binary diff merge=mlAutoMerge +*.mdlp binary +*.mex* binary +*.mlapp binary +*.mldatx binary +*.mlproj binary +*.mlx binary +*.p binary +*.sfx binary +*.sldd binary +*.slreqx binary merge=mlAutoMerge +*.slmx binary merge=mlAutoMerge +*.sltx binary +*.slxc binary +*.slx binary merge=mlAutoMerge +*.slxp binary + +## Other common binary file types +*.docx binary +*.exe binary +*.jpg binary +*.pdf binary +*.png binary +*.xlsx binary diff --git a/src/main/resources/templates/matlab/test/.gitignore b/src/main/resources/templates/matlab/test/.gitignore new file mode 100644 index 000000000000..24cee4ed66ae --- /dev/null +++ b/src/main/resources/templates/matlab/test/.gitignore @@ -0,0 +1,39 @@ +# Test results +results.xml + +# Autosave files +*.asv +*.m~ +*.autosave +*.slx.r* +*.mdl.r* + +# Derived content-obscured files +*.p + +# Compiled MEX files +*.mex* + +# Packaged app and toolbox files +*.mlappinstall +*.mltbx + +# Deployable archives +*.ctf + +# Generated helpsearch folders +helpsearch*/ + +# Code generation folders +slprj/ +sccprj/ +codegen/ + +# Cache files +*.slxc + +# Cloud based storage dotfile +.MATLABDriveTag + +# buildtool cache folder +.buildtool/ diff --git a/src/main/resources/templates/matlab/test/testRunner.m b/src/main/resources/templates/matlab/test/testRunner.m new file mode 100644 index 000000000000..1e409e2de0dd --- /dev/null +++ b/src/main/resources/templates/matlab/test/testRunner.m @@ -0,0 +1,12 @@ +import matlab.unittest.plugins.XMLPlugin + +addpath("../${studentParentWorkingDirectoryName}") + +runner = testrunner; + +plugin = XMLPlugin.producingJUnitFormat("test-results/results.xml"); +addPlugin(runner,plugin); + +suite = testsuite("tests"); + +run(runner,suite); diff --git a/src/main/resources/templates/matlab/test/tests/GradeTest.m b/src/main/resources/templates/matlab/test/tests/GradeTest.m new file mode 100644 index 000000000000..3511c887274f --- /dev/null +++ b/src/main/resources/templates/matlab/test/tests/GradeTest.m @@ -0,0 +1,45 @@ +classdef GradeTest < matlab.unittest.TestCase + properties + grades + end + + methods (TestClassSetup) + % Shared setup for the entire test class + end + + methods (TestMethodSetup) + % Setup for each test + + function setupGrades(testCase) + testCase.grades = [1.3 3.3 4.0 4.7 + 2.7 1.7 4.0 1.7 + 1.7 3.7 3.0 4.3 + 4.3 2.3 1.7 3.3 + 2.3 3.7 2.0 5.0]; + end + end + + methods (Test) + % Test methods + + function testMedianGradeByAssignment(testCase) + actual = medianGradeByAssignment(testCase.grades); + expected = [2.3 3.3 3.0 4.3]; + testCase.assertEqual(actual,expected,"median is incorrect",AbsTol=0.0001); + end + + function testAverageGradeByStudent(testCase) + actual = averageGradeByStudent(testCase.grades); + expected = [3.325 2.525 3.175 2.9 3.25]; + testCase.assertEqual(actual,expected,"average is incorrect",AbsTol=0.0001); + end + + function testFinalGrade(testCase) + weights = [0.1 0.1 0.5 0.3]; + actual = finalGrade(testCase.grades,weights); + expected = [3.9 3.0 3.3 2.5 3.1]; + testCase.assertEqual(actual,expected,"final grades are incorrect",AbsTol=0.0001); + end + end + +end diff --git a/src/main/resources/templates/ruby/exercise/.gitignore b/src/main/resources/templates/ruby/exercise/.gitignore new file mode 100644 index 000000000000..9106b2a345b0 --- /dev/null +++ b/src/main/resources/templates/ruby/exercise/.gitignore @@ -0,0 +1,8 @@ +/.bundle/ +/.yardoc +/_yardoc/ +/coverage/ +/doc/ +/pkg/ +/spec/reports/ +/tmp/ diff --git a/src/main/resources/templates/ruby/exercise/Gemfile b/src/main/resources/templates/ruby/exercise/Gemfile new file mode 100644 index 000000000000..dba00cea4087 --- /dev/null +++ b/src/main/resources/templates/ruby/exercise/Gemfile @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +gem "date", "~> 3.4" diff --git a/src/main/resources/templates/ruby/exercise/Gemfile.lock b/src/main/resources/templates/ruby/exercise/Gemfile.lock new file mode 100644 index 000000000000..af596da80a54 --- /dev/null +++ b/src/main/resources/templates/ruby/exercise/Gemfile.lock @@ -0,0 +1,14 @@ +GEM + remote: https://rubygems.org/ + specs: + date (3.4.1) + +PLATFORMS + ruby + x86_64-linux + +DEPENDENCIES + date (~> 3.4) + +BUNDLED WITH + 2.6.2 diff --git a/src/main/resources/templates/ruby/exercise/src/bubble_sort.rb b/src/main/resources/templates/ruby/exercise/src/bubble_sort.rb new file mode 100644 index 000000000000..5b4f9744ba04 --- /dev/null +++ b/src/main/resources/templates/ruby/exercise/src/bubble_sort.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class BubbleSort + # Sorts dates with BubbleSort. + # @param input [Array] the Array of Dates to be sorted + def perform_sort(input) + # TODO: implement + + raise(NotImplementedError) + end +end diff --git a/src/main/resources/templates/ruby/exercise/src/client.rb b/src/main/resources/templates/ruby/exercise/src/client.rb new file mode 100755 index 000000000000..697afa7fc52c --- /dev/null +++ b/src/main/resources/templates/ruby/exercise/src/client.rb @@ -0,0 +1,66 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require "date" + +module Client + # TODO: Implement BubbleSort + # TODO: Implement MergeSort + + # TODO: Create and implement a Context class according to the UML class diagram + # TODO: Create and implement a Policy class as described in the problem statement + + ITERATIONS = 10 + DATES_LENGTH_MIN = 5 + DATES_LENGTH_MAX = 15 + + # Main method. + # Add code to demonstrate your implementation here. + def self.main + # TODO: Init Context and Policy + + # Run multiple times to simulate different sorting strategies + ITERATIONS.times do + dates = create_random_dates + + # TODO: Configure context + + print("Unsorted Array of dates: ") + print_dates(dates) + + # TODO: Sort dates + + print("Sorted Array of dates: ") + print_dates(dates) + end + end + + # Generates a List of random Date objects with random List size between + # DATES_LENGTH_MIN and DATES_LENGTH_MAX. + # @return [Array] an Array of random Date objects. + def self.create_random_dates + dates_length = Random.rand(DATES_LENGTH_MIN..DATES_LENGTH_MAX) + + lowest_date = Date.new(2024, 9, 15) + highest_date = Date.new(2025, 1, 15) + + Array.new(dates_length) { random_date_within(lowest_date, highest_date) } + end + + # Creates a random Date within the given range. + # @param low [Date] the lower bound + # @param high [Date] the upper bound + # @return [Date] a random Date within the given range + def self.random_date_within(low, high) + random_jd = Random.rand(low.jd..high.jd) + Date.jd(random_jd) + end + + # Prints out the given Array of Date objects. + # @param dates [Array] list of the dates to print + def self.print_dates(dates) + puts(dates.join(", ")) + end +end + +Client.main diff --git a/src/main/resources/templates/ruby/exercise/src/merge_sort.rb b/src/main/resources/templates/ruby/exercise/src/merge_sort.rb new file mode 100644 index 000000000000..25a227ed89c3 --- /dev/null +++ b/src/main/resources/templates/ruby/exercise/src/merge_sort.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class MergeSort + # Sorts dates with MergeSort. + # @param input [Array] the Array of Dates to be sorted + def perform_sort(input) + # TODO: implement + + raise(NotImplementedError) + end +end diff --git a/src/main/resources/templates/ruby/readme b/src/main/resources/templates/ruby/readme new file mode 100644 index 000000000000..ee6e5c451431 --- /dev/null +++ b/src/main/resources/templates/ruby/readme @@ -0,0 +1,89 @@ +# Sorting with the Strategy Pattern + +In this exercise, we want to implement sorting algorithms and choose them based on runtime specific variables. + +### Part 1: Sorting + +First, we need to implement two sorting algorithms, in this case `MergeSort` and `BubbleSort`. + +**You have the following tasks:** + +1. [task][Implement Bubble Sort](test_bubble_sort_sorts) +Implement the method `perform_sort(input: Array[Date])` in the class `BubbleSort`. Make sure to follow the Bubble Sort algorithm exactly. + +2. [task][Implement Merge Sort](test_merge_sort_sorts) +Implement the method `perform_sort(input: Array[Date])` in the class `MergeSort`. Make sure to follow the Merge Sort algorithm exactly. + +### Part 2: Strategy Pattern + +We want the application to apply different algorithms for sorting an `Array` of `Date` objects. +Use the strategy pattern to select the right sorting algorithm at runtime. + +**You have the following tasks:** + +1. [task][Context Class](test_context_structure) +Create and implement a `Context` class in `context.rb` following the class diagram below. +Add read and write accessors for the attributes and associations. + +2. [task][Context Policy](test_policy_structure) +Create and implement a `Policy` class in `policy.rb` following the class diagram below. +Add read and write accessors for the attributes and associations. +`Policy` should implement a simple configuration mechanism: + + 1. [task][Select MergeSort](test_use_merge_sort_for_big_list) + Select `MergeSort` when the Array has more than 10 dates. + + 2. [task][Select BubbleSort](test_use_bubble_sort_for_small_list) + Select `BubbleSort` when the Array has less or equal 10 dates. + +3. Complete the `Client` class which demonstrates switching between two strategies at runtime. + +@startuml + +class Client { +} + +class Policy ##testsColor(test_policy_structure) { + +configure() +} + +class Context ##testsColor(test_context_structure) { + -dates: Array[Date] + +sort() +} + +interface SortStrategy { + +perform_sort(input: Array[Date]) +} + +class BubbleSort { + +perform_sort(input: Array[Date]) +} + +class MergeSort { + +perform_sort(input: Array[Date]) +} + +MergeSort -up-|> SortStrategy +BubbleSort -up-|> SortStrategy +Policy -right-> Context #testsColor(test_policy_structure): context +Context -right-> SortStrategy #testsColor(test_context_structure): sort_algorithm +Client .down.> Policy +Client .down.> Context + +hide empty fields +hide empty methods + +@enduml + + +### Part 3: Optional Challenges + +(These are not tested) + +1. Create a new class `QuickSort` and implement the Quick Sort algorithm. + +2. Make the method `perform_sort(input: Array[Date])` not depend on `Date` objects, so that other objects can also be sorted by the same method. +**Hint:** Have a look at the module `Comparable`. + +3. Think about a useful decision in `Policy` when to use the new `QuickSort` algorithm. diff --git a/src/main/resources/templates/ruby/solution/.gitignore b/src/main/resources/templates/ruby/solution/.gitignore new file mode 100644 index 000000000000..9106b2a345b0 --- /dev/null +++ b/src/main/resources/templates/ruby/solution/.gitignore @@ -0,0 +1,8 @@ +/.bundle/ +/.yardoc +/_yardoc/ +/coverage/ +/doc/ +/pkg/ +/spec/reports/ +/tmp/ diff --git a/src/main/resources/templates/ruby/solution/Gemfile b/src/main/resources/templates/ruby/solution/Gemfile new file mode 100644 index 000000000000..dba00cea4087 --- /dev/null +++ b/src/main/resources/templates/ruby/solution/Gemfile @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +gem "date", "~> 3.4" diff --git a/src/main/resources/templates/ruby/solution/Gemfile.lock b/src/main/resources/templates/ruby/solution/Gemfile.lock new file mode 100644 index 000000000000..af596da80a54 --- /dev/null +++ b/src/main/resources/templates/ruby/solution/Gemfile.lock @@ -0,0 +1,14 @@ +GEM + remote: https://rubygems.org/ + specs: + date (3.4.1) + +PLATFORMS + ruby + x86_64-linux + +DEPENDENCIES + date (~> 3.4) + +BUNDLED WITH + 2.6.2 diff --git a/src/main/resources/templates/ruby/solution/src/bubble_sort.rb b/src/main/resources/templates/ruby/solution/src/bubble_sort.rb new file mode 100644 index 000000000000..ae9a7b5b8da5 --- /dev/null +++ b/src/main/resources/templates/ruby/solution/src/bubble_sort.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class BubbleSort + # Sorts dates with BubbleSort. + # @param input [Array] the Array of Dates to be sorted + def perform_sort(input) + (input.length - 1).downto(0) do |i| + (0...i).each do |j| + input[j], input[j + 1] = input[j + 1], input[j] if input[j] > input[j + 1] + end + end + end +end diff --git a/src/main/resources/templates/ruby/solution/src/client.rb b/src/main/resources/templates/ruby/solution/src/client.rb new file mode 100755 index 000000000000..695fbc348da9 --- /dev/null +++ b/src/main/resources/templates/ruby/solution/src/client.rb @@ -0,0 +1,64 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require "date" + +require_relative "context" +require_relative "policy" + +module Client + ITERATIONS = 10 + DATES_LENGTH_MIN = 5 + DATES_LENGTH_MAX = 15 + + # Main method. + # Add code to demonstrate your implementation here. + def self.main + context = Context.new + policy = Policy.new(context) + + ITERATIONS.times do + dates = create_random_dates + + context.dates = dates + policy.configure + + print("Unsorted Array of dates: ") + print_dates(dates) + + context.sort + + print("Sorted Array of dates: ") + print_dates(dates) + end + end + + # Generates a List of random Date objects with random List size between + # DATES_LENGTH_MIN and DATES_LENGTH_MAX. + # @return [Array] an Array of random Date objects. + def self.create_random_dates + dates_length = Random.rand(DATES_LENGTH_MIN..DATES_LENGTH_MAX) + + lowest_date = Date.new(2024, 9, 15) + highest_date = Date.new(2025, 1, 15) + + Array.new(dates_length) { random_date_within(lowest_date, highest_date) } + end + + # Creates a random Date within the given range. + # @param low [Date] the lower bound + # @param high [Date] the upper bound + # @return [Date] a random Date within the given range + def self.random_date_within(low, high) + random_jd = Random.rand(low.jd..high.jd) + Date.jd(random_jd) + end + + # Prints out the given Array of Date objects. + # @param dates [Array] list of the dates to print + def self.print_dates(dates) + puts(dates.join(", ")) + end +end + +Client.main diff --git a/src/main/resources/templates/ruby/solution/src/context.rb b/src/main/resources/templates/ruby/solution/src/context.rb new file mode 100644 index 000000000000..09a51bdc6861 --- /dev/null +++ b/src/main/resources/templates/ruby/solution/src/context.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class Context + attr_accessor :dates, :sort_algorithm + + # Runs the configured sort algorithm. + def sort + raise "sort_algorithm not set" if @sort_algorithm.nil? + raise "dates not set" if @dates.nil? + + @sort_algorithm.perform_sort(@dates) + end +end diff --git a/src/main/resources/templates/ruby/solution/src/merge_sort.rb b/src/main/resources/templates/ruby/solution/src/merge_sort.rb new file mode 100644 index 000000000000..7e2b70286d46 --- /dev/null +++ b/src/main/resources/templates/ruby/solution/src/merge_sort.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +class MergeSort + # Wrapper method for the real MergeSort algorithm. + # @param input [Array] the Array of Dates to be sorted + def perform_sort(input) + mergesort(input, 0, input.length) + end + + private + + # Recursive merge sort method + def mergesort(input, low, high) + return if high - low <= 1 + + mid = (low + high) / 2 + + mergesort(input, low, mid) + mergesort(input, mid, high) + merge(input, low, mid, high) + end + + # Merge method + def merge(input, low, middle, high) + temp = Array.new(high - low) + + left_index = low + right_index = middle + whole_index = 0 + + while left_index < middle && right_index < high + if input[left_index] <= input[right_index] + temp[whole_index] = input[left_index] + left_index += 1 + else + temp[whole_index] = input[right_index] + right_index += 1 + end + whole_index += 1 + end + + while left_index < middle + temp[whole_index] = input[left_index] + left_index += 1 + whole_index += 1 + end + + while right_index < high + temp[whole_index] = input[right_index] + right_index += 1 + whole_index += 1 + end + + temp.each_with_index do |value, index| + input[low + index] = value + end + end +end diff --git a/src/main/resources/templates/ruby/solution/src/policy.rb b/src/main/resources/templates/ruby/solution/src/policy.rb new file mode 100644 index 000000000000..c8ba20b4105c --- /dev/null +++ b/src/main/resources/templates/ruby/solution/src/policy.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require_relative "bubble_sort" +require_relative "merge_sort" + +class Policy + attr_accessor :context + + DATES_SIZE_THRESHOLD = 10 + + def initialize(context) + @context = context + end + + # Chooses a strategy depending on the number of date objects. + def configure + sort_algorithm = if @context.dates.length > DATES_SIZE_THRESHOLD + MergeSort.new + else + BubbleSort.new + end + @context.sort_algorithm = sort_algorithm + end +end diff --git a/src/main/resources/templates/ruby/test/.gitignore b/src/main/resources/templates/ruby/test/.gitignore new file mode 100644 index 000000000000..f7edf22658b3 --- /dev/null +++ b/src/main/resources/templates/ruby/test/.gitignore @@ -0,0 +1,11 @@ +/report.xml +/rubocop.sarif + +/.bundle/ +/.yardoc +/_yardoc/ +/coverage/ +/doc/ +/pkg/ +/spec/reports/ +/tmp/ diff --git a/src/main/resources/templates/ruby/test/Gemfile b/src/main/resources/templates/ruby/test/Gemfile new file mode 100644 index 000000000000..98ac822b9f28 --- /dev/null +++ b/src/main/resources/templates/ruby/test/Gemfile @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +gem "rake", "~> 13.0" + +gem "minitest", "~> 5.16" +gem "minitest-junit", "~> 2.1" + +gem "date", "~> 3.4" diff --git a/src/main/resources/templates/ruby/test/Gemfile.lock b/src/main/resources/templates/ruby/test/Gemfile.lock new file mode 100644 index 000000000000..761adfe3c6f4 --- /dev/null +++ b/src/main/resources/templates/ruby/test/Gemfile.lock @@ -0,0 +1,25 @@ +GEM + remote: https://rubygems.org/ + specs: + bigdecimal (3.1.9) + date (3.4.1) + minitest (5.25.4) + minitest-junit (2.1.0) + minitest (~> 5.11) + ox (~> 2, >= 2.14.2) + ox (2.14.20) + bigdecimal (>= 3.0) + rake (13.2.1) + +PLATFORMS + ruby + x86_64-linux + +DEPENDENCIES + date (~> 3.4) + minitest (~> 5.16) + minitest-junit (~> 2.1) + rake (~> 13.0) + +BUNDLED WITH + 2.6.2 diff --git a/src/main/resources/templates/ruby/test/Rakefile b/src/main/resources/templates/ruby/test/Rakefile new file mode 100644 index 000000000000..1066ffc918ca --- /dev/null +++ b/src/main/resources/templates/ruby/test/Rakefile @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require_relative "assignment_path" + +require "minitest/test_task" + +Minitest::TestTask.create :test + +namespace :ci do + Minitest::TestTask.create :test do |task| + task.extra_args = ["--verbose", "--junit", "--junit-jenkins", "--junit-filename=report.xml"] + end +end + +task default: %i[test] +task ci: %i[ci:test] diff --git a/src/main/resources/templates/ruby/test/assignment_path.rb b/src/main/resources/templates/ruby/test/assignment_path.rb new file mode 100644 index 000000000000..55e8c0b163ab --- /dev/null +++ b/src/main/resources/templates/ruby/test/assignment_path.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +ASSIGNMENT_PATH = File.expand_path("../${studentParentWorkingDirectoryName}", __dir__) diff --git a/src/main/resources/templates/ruby/test/test/test_behavior.rb b/src/main/resources/templates/ruby/test/test/test_behavior.rb new file mode 100644 index 000000000000..0c76fcf42b74 --- /dev/null +++ b/src/main/resources/templates/ruby/test/test/test_behavior.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +require_relative "test_helper" + +require "date" + +class TestBehavior < Minitest::Test + def setup + @dates = [ + Date.new(2018, 11, 8), + Date.new(2017, 4, 15), + Date.new(2016, 2, 15), + Date.new(2017, 9, 15) + ] + @ordered_dates = [ + Date.new(2016, 2, 15), + Date.new(2017, 4, 15), + Date.new(2017, 9, 15), + Date.new(2018, 11, 8) + ] + end + + def test_bubble_sort_sorts + require "bubble_sort" + + bubble_sort = BubbleSort.new + bubble_sort.perform_sort(@dates) + assert_equal(@ordered_dates, @dates, "dates were not sorted") + end + + def test_merge_sort_sorts + require "merge_sort" + + merge_sort = MergeSort.new + merge_sort.perform_sort(@dates) + assert_equal(@ordered_dates, @dates, "dates were not sorted") + end + + def test_use_merge_sort_for_big_list + require "context" + require "policy" + require "bubble_sort" + + dates = Array.new(11, 0) + context = Context.new + context.dates = dates + policy = Policy.new(context) + policy.configure + assert(context.sort_algorithm.instance_of?(MergeSort), "selected algorithm was not MergeSort") + end + + def test_use_bubble_sort_for_small_list + require "context" + require "policy" + require "merge_sort" + + dates = Array.new(3, 0) + context = Context.new + context.dates = dates + policy = Policy.new(context) + policy.configure + assert(context.sort_algorithm.instance_of?(BubbleSort), "selected algorithm was not BubbleSort") + end +end diff --git a/src/main/resources/templates/ruby/test/test/test_helper.rb b/src/main/resources/templates/ruby/test/test/test_helper.rb new file mode 100644 index 000000000000..8ab2ec04aeb9 --- /dev/null +++ b/src/main/resources/templates/ruby/test/test/test_helper.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +require_relative "../assignment_path" + +$LOAD_PATH.unshift File.join(ASSIGNMENT_PATH, "src") + +require "minitest/autorun" + +# exit successfully on failure +Minitest.after_run do + exit 0 +end diff --git a/src/main/resources/templates/ruby/test/test/test_structural.rb b/src/main/resources/templates/ruby/test/test/test_structural.rb new file mode 100644 index 000000000000..417522f29efe --- /dev/null +++ b/src/main/resources/templates/ruby/test/test/test_structural.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require_relative "test_helper" + +class TestStructural < Minitest::Test + def test_context_structure + require "context" + + assert(defined?(Context), "Context is not defined") + assert(Context.instance_of?(Class), "Context is not a class") + assert_equal([], Context.instance_method(:initialize).parameters, + "The constructor of Context does not have the expected parameters") + assert(Context.public_method_defined?(:dates), "Context has not attribute reader for 'dates'") + assert(Context.public_method_defined?(:dates=), "Context has not attribute writer for 'dates'") + assert(Context.public_method_defined?(:sort_algorithm), "Context has not attribute reader for 'sort_algorithm'") + assert(Context.public_method_defined?(:sort_algorithm=), "Context has not attribute writer for 'sort_algorithm'") + assert(Context.public_method_defined?(:sort), "Context has no method 'sort'") + end + + def test_policy_structure + require "policy" + + assert(defined?(Policy), "Policy is not defined") + assert(Policy.instance_of?(Class), "Policy is not a class") + assert_equal([%i[req context]], Policy.instance_method(:initialize).parameters, + "The constructor of Policy does not have the expected parameters") + assert(Policy.public_method_defined?(:context), "Policy has not attribute reader for 'context'") + assert(Policy.public_method_defined?(:context=), "Policy has not attribute writer for 'context'") + assert(Policy.public_method_defined?(:configure), "Policy has no method 'configure'") + end +end diff --git a/src/main/webapp/app/app.routes.ts b/src/main/webapp/app/app.routes.ts index 12e05fc20795..1ee5b6003bfe 100644 --- a/src/main/webapp/app/app.routes.ts +++ b/src/main/webapp/app/app.routes.ts @@ -183,7 +183,7 @@ const routes: Routes = [ // ===== EXAM ===== { path: 'course-management/:courseId/exams', - loadChildren: () => import('./exam/manage/exam-management.module').then((m) => m.ArtemisExamManagementModule), + loadChildren: () => import('./exam/manage/exam-management.route').then((m) => m.examManagementRoute), }, { path: 'courses/:courseId/exams/:examId/grading-system', diff --git a/src/main/webapp/app/course/manage/course-management.route.ts b/src/main/webapp/app/course/manage/course-management.route.ts index eb8a2f8ccf7b..fc55b6daf8c3 100644 --- a/src/main/webapp/app/course/manage/course-management.route.ts +++ b/src/main/webapp/app/course/manage/course-management.route.ts @@ -76,7 +76,7 @@ export const courseManagementState: Routes = [ }, { path: ':courseId/exams', - loadChildren: () => import('../../exam/manage/exam-management.module').then((m) => m.ArtemisExamManagementModule), + loadChildren: () => import('../../exam/manage/exam-management.route').then((m) => m.examManagementRoute), }, { path: ':courseId/tutorial-groups-checklist', diff --git a/src/main/webapp/app/entities/programming/programming-exercise.model.ts b/src/main/webapp/app/entities/programming/programming-exercise.model.ts index ece9e6d6989c..b07d272ff26a 100644 --- a/src/main/webapp/app/entities/programming/programming-exercise.model.ts +++ b/src/main/webapp/app/entities/programming/programming-exercise.model.ts @@ -23,9 +23,11 @@ export enum ProgrammingLanguage { JAVA = 'JAVA', JAVASCRIPT = 'JAVASCRIPT', KOTLIN = 'KOTLIN', + MATLAB = 'MATLAB', OCAML = 'OCAML', PYTHON = 'PYTHON', R = 'R', + RUBY = 'RUBY', RUST = 'RUST', SWIFT = 'SWIFT', TYPESCRIPT = 'TYPESCRIPT', diff --git a/src/main/webapp/app/exam/exam-scores/exam-scores.route.ts b/src/main/webapp/app/exam/exam-scores/exam-scores.route.ts deleted file mode 100644 index ac35ee9c1370..000000000000 --- a/src/main/webapp/app/exam/exam-scores/exam-scores.route.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Route, Routes } from '@angular/router'; - -import { Authority } from 'app/shared/constants/authority.constants'; -import { UserRouteAccessService } from 'app/core/auth/user-route-access-service'; - -export const examScoresRoute: Route[] = [ - { - path: ':examId/scores', - loadComponent: () => import('app/exam/exam-scores/exam-scores.component').then((m) => m.ExamScoresComponent), - }, -]; - -const EXAM_SCORES_ROUTES = [...examScoresRoute]; - -export const examScoresState: Routes = [ - { - path: '', - children: EXAM_SCORES_ROUTES, - data: { - authorities: [Authority.ADMIN, Authority.INSTRUCTOR], - pageTitle: 'artemisApp.examScores.title', - }, - canActivate: [UserRouteAccessService], - }, -]; diff --git a/src/main/webapp/app/exam/manage/exam-management.module.ts b/src/main/webapp/app/exam/manage/exam-management.module.ts deleted file mode 100644 index 6118a2fd855e..000000000000 --- a/src/main/webapp/app/exam/manage/exam-management.module.ts +++ /dev/null @@ -1,142 +0,0 @@ -import { NgModule } from '@angular/core'; -import { RouterModule } from '@angular/router'; -import { ExamManagementComponent } from 'app/exam/manage/exam-management.component'; -import { examManagementState } from 'app/exam/manage/exam-management.route'; -import { ExamUpdateComponent } from 'app/exam/manage/exams/exam-update.component'; -import { ExamDetailComponent } from 'app/exam/manage/exams/exam-detail.component'; -import { ExerciseGroupsComponent } from 'app/exam/manage/exercise-groups/exercise-groups.component'; -import { ExerciseGroupUpdateComponent } from 'app/exam/manage/exercise-groups/exercise-group-update.component'; -import { ExamStudentsComponent } from 'app/exam/manage/students/exam-students.component'; -import { StudentExamsComponent } from 'app/exam/manage/student-exams/student-exams.component'; -import { StudentExamDetailComponent } from 'app/exam/manage/student-exams/student-exam-detail.component'; -import { StudentsUploadImagesModule } from 'app/exam/manage/students/upload-images/students-upload-images.module'; -import { ExamStudentsAttendanceCheckComponent } from 'app/exam/manage/students/verify-attendance-check/exam-students-attendance-check.component'; -import { ArtemisTextExerciseModule } from 'app/exercises/text/manage/text-exercise/text-exercise.module'; -import { ArtemisFileUploadExerciseManagementModule } from 'app/exercises/file-upload/manage/file-upload-exercise-management.module'; -import { ArtemisProgrammingExerciseManagementModule } from 'app/exercises/programming/manage/programming-exercise-management.module'; -import { ArtemisQuizManagementModule } from 'app/exercises/quiz/manage/quiz-management.module'; -import { ArtemisSharedModule } from 'app/shared/shared.module'; -import { ArtemisDataTableModule } from 'app/shared/data-table/data-table.module'; -import { NgxDatatableModule } from '@siemens/ngx-datatable'; -import { FormDateTimePickerModule } from 'app/shared/date-time-picker/date-time-picker.module'; -import { ArtemisSharedComponentModule } from 'app/shared/components/shared-component.module'; -import { ArtemisMarkdownEditorModule } from 'app/shared/markdown-editor/markdown-editor.module'; -import { DurationPipe } from 'app/shared/pipes/artemis-duration.pipe'; -import { StudentExamStatusComponent } from 'app/exam/manage/student-exams/student-exam-status/student-exam-status.component'; -import { StudentExamSummaryComponent } from 'app/exam/manage/student-exams/student-exam-summary.component'; -import { ArtemisParticipationSummaryModule } from 'app/exam/participate/summary/exam-result-summary.module'; -import { ExamExerciseRowButtonsComponent } from 'app/exercises/shared/exam-exercise-row-buttons/exam-exercise-row-buttons.component'; -import { ArtemisProgrammingExerciseStatusModule } from 'app/exercises/programming/manage/status/programming-exercise-status.module'; -import { ArtemisMarkdownModule } from 'app/shared/markdown.module'; -import { ExamChecklistComponent } from 'app/exam/manage/exams/exam-checklist-component/exam-checklist.component'; -import { ExamChecklistExerciseGroupTableComponent } from 'app/exam/manage/exams/exam-checklist-component/exam-checklist-exercisegroup-table/exam-checklist-exercisegroup-table.component'; -import { ArtemisTutorParticipationGraphModule } from 'app/shared/dashboards/tutor-participation-graph/tutor-participation-graph.module'; -import { ProgrammingExerciseGroupCellComponent } from './exercise-groups/programming-exercise-cell/programming-exercise-group-cell.component'; -import { FileUploadExerciseGroupCellComponent } from './exercise-groups/file-upload-exercise-cell/file-upload-exercise-group-cell.component'; -import { ModelingExerciseGroupCellComponent } from './exercise-groups/modeling-exercise-cell/modeling-exercise-group-cell.component'; -import { QuizExerciseGroupCellComponent } from './exercise-groups/quiz-exercise-cell/quiz-exercise-group-cell.component'; -import { ArtemisTextSubmissionAssessmentModule } from 'app/exercises/text/assess/text-submission-assessment.module'; -import { StudentExamDetailTableRowComponent } from 'app/exam/manage/student-exams/student-exam-detail-table-row/student-exam-detail-table-row.component'; -import { ExampleSubmissionsModule } from 'app/exercises/shared/example-submission/example-submissions.module'; -import { BarChartModule } from '@swimlane/ngx-charts'; -import { UserImportModule } from 'app/shared/user-import/user-import.module'; -import { ArtemisExamSharedModule } from 'app/exam/shared/exam-shared.module'; -import { ExamStatusComponent } from 'app/exam/manage/exam-status.component'; -import { ArtemisExamModePickerModule } from 'app/exam/manage/exams/exam-mode-picker/exam-mode-picker.module'; -import { ExamImportComponent } from 'app/exam/manage/exams/exam-import/exam-import.component'; -import { ArtemisHeaderExercisePageWithDetailsModule } from 'app/exercises/shared/exercise-headers/exercise-headers.module'; -import { ExamExerciseImportComponent } from 'app/exam/manage/exams/exam-exercise-import/exam-exercise-import.component'; - -import { BonusComponent } from 'app/grading-system/bonus/bonus.component'; -import { StudentExamTimelineComponent } from './student-exams/student-exam-timeline/student-exam-timeline.component'; -import { TitleChannelNameModule } from 'app/shared/form/title-channel-name/title-channel-name.module'; -import { ExamEditWorkingTimeDialogComponent } from 'app/exam/manage/exams/exam-checklist-component/exam-edit-workingtime-dialog/exam-edit-working-time-dialog.component'; -import { ExamEditWorkingTimeComponent } from 'app/exam/manage/exams/exam-checklist-component/exam-edit-workingtime-dialog/exam-edit-working-time.component'; -import { ExamLiveAnnouncementCreateModalComponent } from 'app/exam/manage/exams/exam-checklist-component/exam-announcement-dialog/exam-live-announcement-create-modal.component'; -import { ExamLiveAnnouncementCreateButtonComponent } from 'app/exam/manage/exams/exam-checklist-component/exam-announcement-dialog/exam-live-announcement-create-button.component'; - -import { ArtemisExamNavigationBarModule } from 'app/exam/participate/exam-navigation-bar/exam-navigation-bar.module'; -import { ArtemisExamSubmissionComponentsModule } from 'app/exam/participate/exercises/exam-submission-components.module'; -import { MatSliderModule } from '@angular/material/slider'; -import { ProgrammingExerciseExamDiffComponent } from './student-exams/student-exam-timeline/programming-exam-diff/programming-exercise-exam-diff.component'; -import { ArtemisProgrammingExerciseModule } from 'app/exercises/programming/shared/programming-exercise.module'; -import { DetailModule } from 'app/detail-overview-list/detail.module'; -import { ArtemisDurationFromSecondsPipe } from 'app/shared/pipes/artemis-duration-from-seconds.pipe'; -import { NoDataComponent } from 'app/shared/no-data-component'; -import { SafeHtmlPipe } from 'app/shared/pipes/safe-html.pipe'; -import { GradeStepBoundsPipe } from 'app/shared/pipes/grade-step-bounds.pipe'; -import { examScoresState } from 'app/exam/exam-scores/exam-scores.route'; -import { GitDiffLineStatComponent } from 'app/exercises/programming/git-diff-report/git-diff-line-stat.component'; - -const ENTITY_STATES = [...examManagementState, ...examScoresState]; - -@NgModule({ - // TODO: For better modularization we could define an exercise module with the corresponding exam routes - providers: [ArtemisDurationFromSecondsPipe, SafeHtmlPipe, GradeStepBoundsPipe], - imports: [ - RouterModule.forChild(ENTITY_STATES), - ArtemisTextExerciseModule, - ArtemisSharedModule, - FormDateTimePickerModule, - ArtemisSharedComponentModule, - ArtemisMarkdownEditorModule, - NgxDatatableModule, - ArtemisDataTableModule, - ArtemisFileUploadExerciseManagementModule, - ArtemisProgrammingExerciseManagementModule, - ArtemisQuizManagementModule, - ArtemisParticipationSummaryModule, - ArtemisProgrammingExerciseStatusModule, - ArtemisMarkdownModule, - ArtemisTutorParticipationGraphModule, - ArtemisTextSubmissionAssessmentModule, - ExampleSubmissionsModule, - UserImportModule, - ArtemisExamSharedModule, - ArtemisExamModePickerModule, - ArtemisHeaderExercisePageWithDetailsModule, - BarChartModule, - StudentsUploadImagesModule, - TitleChannelNameModule, - ArtemisExamNavigationBarModule, - ArtemisExamSubmissionComponentsModule, - MatSliderModule, - ArtemisProgrammingExerciseModule, - DetailModule, - NoDataComponent, - GitDiffLineStatComponent, - SafeHtmlPipe, - GradeStepBoundsPipe, - BonusComponent, - ExamManagementComponent, - ExamUpdateComponent, - ExamDetailComponent, - ExerciseGroupsComponent, - ExerciseGroupUpdateComponent, - ExamExerciseRowButtonsComponent, - ExamStudentsComponent, - ExamStudentsAttendanceCheckComponent, - StudentExamStatusComponent, - StudentExamsComponent, - StudentExamDetailComponent, - DurationPipe, - StudentExamSummaryComponent, - ExamChecklistComponent, - ExamChecklistExerciseGroupTableComponent, - ExamStatusComponent, - ProgrammingExerciseGroupCellComponent, - FileUploadExerciseGroupCellComponent, - ModelingExerciseGroupCellComponent, - QuizExerciseGroupCellComponent, - StudentExamDetailTableRowComponent, - ExamImportComponent, - ExamExerciseImportComponent, - ExamEditWorkingTimeComponent, - ExamEditWorkingTimeDialogComponent, - ExamLiveAnnouncementCreateModalComponent, - ExamLiveAnnouncementCreateButtonComponent, - StudentExamTimelineComponent, - ProgrammingExerciseExamDiffComponent, - ], -}) -export class ArtemisExamManagementModule {} diff --git a/src/main/webapp/app/exam/manage/exam-management.route.ts b/src/main/webapp/app/exam/manage/exam-management.route.ts index 6b8cd746e9ae..47113ae86d0f 100644 --- a/src/main/webapp/app/exam/manage/exam-management.route.ts +++ b/src/main/webapp/app/exam/manage/exam-management.route.ts @@ -63,6 +63,15 @@ export const examManagementRoute: Routes = [ }, canActivate: [UserRouteAccessService], }, + { + path: ':examId/scores', + loadComponent: () => import('app/exam/exam-scores/exam-scores.component').then((m) => m.ExamScoresComponent), + data: { + authorities: [Authority.ADMIN, Authority.INSTRUCTOR], + pageTitle: 'artemisApp.examScores.title', + }, + canActivate: [UserRouteAccessService], + }, { path: ':examId', loadComponent: () => import('app/exam/manage/exams/exam-detail.component').then((m) => m.ExamDetailComponent), @@ -996,12 +1005,3 @@ export const examManagementRoute: Routes = [ canActivate: [UserRouteAccessService], }, ]; - -const EXAM_MANAGEMENT_ROUTES = [...examManagementRoute]; - -export const examManagementState: Routes = [ - { - path: '', - children: EXAM_MANAGEMENT_ROUTES, - }, -]; diff --git a/src/main/webapp/app/exam/manage/exams/exam-detail.component.ts b/src/main/webapp/app/exam/manage/exams/exam-detail.component.ts index cbf58cbb97b2..5ad8f10f0e3b 100644 --- a/src/main/webapp/app/exam/manage/exams/exam-detail.component.ts +++ b/src/main/webapp/app/exam/manage/exams/exam-detail.component.ts @@ -14,7 +14,7 @@ import { faAward, faClipboard, faEye, faFlaskVial, faHeartBroken, faListAlt, faT import { AlertService } from 'app/core/util/alert.service'; import { GradingSystemService } from 'app/grading-system/grading-system.service'; import { GradeType } from 'app/entities/grading-scale.model'; -import { DetailOverviewSection, DetailType } from 'app/detail-overview-list/detail-overview-list.component'; +import { DetailOverviewListComponent, DetailOverviewSection, DetailType } from 'app/detail-overview-list/detail-overview-list.component'; import { ArtemisDurationFromSecondsPipe } from 'app/shared/pipes/artemis-duration-from-seconds.pipe'; import { scrollToTopOfPage } from 'app/shared/util/utils'; import { ExerciseType } from 'app/entities/exercise.model'; @@ -23,12 +23,12 @@ import { FaIconComponent } from '@fortawesome/angular-fontawesome'; import { DeleteButtonDirective } from 'app/shared/delete-dialog/delete-button.directive'; import { CourseExamArchiveButtonComponent } from 'app/shared/components/course-exam-archive-button/course-exam-archive-button.component'; import { ExamChecklistComponent } from './exam-checklist-component/exam-checklist.component'; -import { DetailOverviewListComponent } from 'app/detail-overview-list/detail-overview-list.component'; @Component({ selector: 'jhi-exam-detail', templateUrl: './exam-detail.component.html', imports: [TranslateDirective, RouterLink, FaIconComponent, DeleteButtonDirective, CourseExamArchiveButtonComponent, ExamChecklistComponent, DetailOverviewListComponent], + providers: [ArtemisDurationFromSecondsPipe], }) export class ExamDetailComponent implements OnInit, OnDestroy { private route = inject(ActivatedRoute); diff --git a/src/main/webapp/app/exam/manage/exams/exam-mode-picker/exam-mode-picker.module.ts b/src/main/webapp/app/exam/manage/exams/exam-mode-picker/exam-mode-picker.module.ts deleted file mode 100644 index 50ab25c83469..000000000000 --- a/src/main/webapp/app/exam/manage/exams/exam-mode-picker/exam-mode-picker.module.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { NgModule } from '@angular/core'; - -import { ExamModePickerComponent } from 'app/exam/manage/exams/exam-mode-picker/exam-mode-picker.component'; -import { ArtemisSharedModule } from 'app/shared/shared.module'; - -@NgModule({ - imports: [ArtemisSharedModule, ExamModePickerComponent], - exports: [ExamModePickerComponent], -}) -export class ArtemisExamModePickerModule {} diff --git a/src/main/webapp/app/exam/manage/students/upload-images/students-upload-images.module.ts b/src/main/webapp/app/exam/manage/students/upload-images/students-upload-images.module.ts deleted file mode 100644 index cbad103c8eef..000000000000 --- a/src/main/webapp/app/exam/manage/students/upload-images/students-upload-images.module.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { StudentsUploadImagesButtonComponent } from 'app/exam/manage/students/upload-images/students-upload-images-button.component'; -import { StudentsUploadImagesDialogComponent } from 'app/exam/manage/students/upload-images/students-upload-images-dialog.component'; -import { NgModule } from '@angular/core'; -import { ArtemisSharedComponentModule } from 'app/shared/components/shared-component.module'; -import { ArtemisSharedCommonModule } from 'app/shared/shared-common.module'; - -@NgModule({ - imports: [ArtemisSharedComponentModule, ArtemisSharedCommonModule, StudentsUploadImagesDialogComponent, StudentsUploadImagesButtonComponent], - exports: [StudentsUploadImagesDialogComponent, StudentsUploadImagesButtonComponent], -}) -export class StudentsUploadImagesModule {} diff --git a/src/main/webapp/app/exam/participate/events/exam-live-events.module.ts b/src/main/webapp/app/exam/participate/events/exam-live-events.module.ts deleted file mode 100644 index cc805d9c46f0..000000000000 --- a/src/main/webapp/app/exam/participate/events/exam-live-events.module.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { ArtemisSharedCommonModule } from 'app/shared/shared-common.module'; -import { ArtemisExamTimerModule } from 'app/exam/participate/timer/exam-timer.module'; -import { ExamLiveEventsButtonComponent } from 'app/exam/participate/events/exam-live-events-button.component'; -import { ExamLiveEventsOverlayComponent } from 'app/exam/participate/events/exam-live-events-overlay.component'; -import { ArtemisExamSharedModule } from 'app/exam/shared/exam-shared.module'; - -@NgModule({ - imports: [CommonModule, ArtemisSharedCommonModule, ArtemisExamTimerModule, ArtemisExamSharedModule, ExamLiveEventsButtonComponent, ExamLiveEventsOverlayComponent], - exports: [ExamLiveEventsButtonComponent, ExamLiveEventsOverlayComponent], -}) -export class ArtemisExamLiveEventsModule {} diff --git a/src/main/webapp/app/exam/participate/exam-bar/exam-bar.component.ts b/src/main/webapp/app/exam/participate/exam-bar/exam-bar.component.ts index e4c0b2afacf5..b2846149bacb 100644 --- a/src/main/webapp/app/exam/participate/exam-bar/exam-bar.component.ts +++ b/src/main/webapp/app/exam/participate/exam-bar/exam-bar.component.ts @@ -1,18 +1,18 @@ import { AfterViewInit, Component, ElementRef, EventEmitter, Input, OnInit, Output, inject } from '@angular/core'; import { CommonModule } from '@angular/common'; import { ArtemisSharedCommonModule } from 'app/shared/shared-common.module'; -import { ArtemisExamTimerModule } from 'app/exam/participate/timer/exam-timer.module'; -import { ArtemisExamLiveEventsModule } from 'app/exam/participate/events/exam-live-events.module'; import { ExamParticipationService } from 'app/exam/participate/exam-participation.service'; import { Exercise, ExerciseType } from 'app/entities/exercise.model'; import { faDoorClosed } from '@fortawesome/free-solid-svg-icons'; import dayjs from 'dayjs/esm'; import { Exam } from 'app/entities/exam/exam.model'; import { StudentExam } from 'app/entities/student-exam.model'; +import { ExamTimerComponent } from 'app/exam/participate/timer/exam-timer.component'; +import { ExamLiveEventsButtonComponent } from 'app/exam/participate/events/exam-live-events-button.component'; @Component({ selector: 'jhi-exam-bar', - imports: [CommonModule, ArtemisSharedCommonModule, ArtemisExamTimerModule, ArtemisExamLiveEventsModule], + imports: [CommonModule, ArtemisSharedCommonModule, ExamTimerComponent, ExamLiveEventsButtonComponent], templateUrl: './exam-bar.component.html', styleUrl: './exam-bar.component.scss', }) diff --git a/src/main/webapp/app/exam/participate/exam-navigation-bar/exam-navigation-bar.module.ts b/src/main/webapp/app/exam/participate/exam-navigation-bar/exam-navigation-bar.module.ts deleted file mode 100644 index e56eeac54ab9..000000000000 --- a/src/main/webapp/app/exam/participate/exam-navigation-bar/exam-navigation-bar.module.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { ExamNavigationBarComponent } from 'app/exam/participate/exam-navigation-bar/exam-navigation-bar.component'; -import { ArtemisSharedCommonModule } from 'app/shared/shared-common.module'; -import { ArtemisExamTimerModule } from 'app/exam/participate/timer/exam-timer.module'; -import { ArtemisExamLiveEventsModule } from 'app/exam/participate/events/exam-live-events.module'; - -@NgModule({ - imports: [CommonModule, ArtemisSharedCommonModule, ArtemisExamTimerModule, ArtemisExamLiveEventsModule, ExamNavigationBarComponent], - exports: [ExamNavigationBarComponent], -}) -export class ArtemisExamNavigationBarModule {} diff --git a/src/main/webapp/app/exam/participate/exam-participation.component.ts b/src/main/webapp/app/exam/participate/exam-participation.component.ts index 69dde679bdbb..60601da9796f 100644 --- a/src/main/webapp/app/exam/participate/exam-participation.component.ts +++ b/src/main/webapp/app/exam/participate/exam-participation.component.ts @@ -15,7 +15,7 @@ import { Submission } from 'app/entities/submission.model'; import { Exam } from 'app/entities/exam/exam.model'; import { ArtemisServerDateService } from 'app/shared/server-date.service'; import { StudentParticipation } from 'app/entities/participation/student-participation.model'; -import { BehaviorSubject, Observable, Subject, Subscription, of, throwError } from 'rxjs'; +import { BehaviorSubject, Observable, Subject, Subscription, combineLatest, of, throwError } from 'rxjs'; import { catchError, distinctUntilChanged, filter, map, tap, throttleTime, timeout } from 'rxjs/operators'; import { InitializationState } from 'app/entities/participation/participation.model'; import { ProgrammingExercise } from 'app/entities/programming/programming-exercise.model'; @@ -210,8 +210,12 @@ export class ExamParticipationComponent implements OnInit, OnDestroy, ComponentC * loads the exam from the server and initializes the view */ ngOnInit(): void { - this.route.parent?.parent?.params.subscribe((params) => { - this.courseId = parseInt(params['courseId'], 10); + combineLatest({ + parentParams: this.route.parent?.parent?.params ?? of({ courseId: undefined }), + currentParams: this.route.params, + }).subscribe(({ parentParams, currentParams }) => { + const courseId = currentParams['courseId'] || parentParams['courseId']; + this.courseId = parseInt(courseId, 10); }); this.route.params.subscribe((params) => { this.examId = parseInt(params['examId'], 10); diff --git a/src/main/webapp/app/exam/participate/exam-participation.module.ts b/src/main/webapp/app/exam/participate/exam-participation.module.ts deleted file mode 100644 index 980a151486e4..000000000000 --- a/src/main/webapp/app/exam/participate/exam-participation.module.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { NgModule } from '@angular/core'; -import { RouterModule } from '@angular/router'; - -import { ArtemisSharedCommonModule } from 'app/shared/shared-common.module'; -import { ExamParticipationComponent } from 'app/exam/participate/exam-participation.component'; -import { ExamParticipationCoverComponent } from './exam-cover/exam-participation-cover.component'; -import { examParticipationState } from 'app/exam/participate/exam-participation.route'; -import { ArtemisQuizQuestionTypesModule } from 'app/exercises/quiz/shared/questions/artemis-quiz-question-types.module'; -import { ArtemisSharedModule } from 'app/shared/shared.module'; -import { ArtemisModelingEditorModule } from 'app/exercises/modeling/shared/modeling-editor.module'; -import { ArtemisFullscreenModule } from 'app/shared/fullscreen/fullscreen.module'; -import { ArtemisProgrammingAssessmentModule } from 'app/exercises/programming/assess/programming-assessment.module'; -import { ArtemisSharedComponentModule } from 'app/shared/components/shared-component.module'; -import { ArtemisProgrammingParticipationModule } from 'app/exercises/programming/participate/programming-participation.module'; -import { ArtemisCodeEditorModule } from 'app/exercises/programming/shared/code-editor/code-editor.module'; -import { ArtemisResultModule } from 'app/exercises/shared/result/result.module'; -import { ArtemisProgrammingExerciseActionsModule } from 'app/exercises/programming/shared/actions/programming-exercise-actions.module'; -import { ArtemisParticipationSummaryModule } from 'app/exam/participate/summary/exam-result-summary.module'; -import { ArtemisExerciseButtonsModule } from 'app/overview/exercise-details/exercise-buttons.module'; -import { ArtemisHeaderExercisePageWithDetailsModule } from 'app/exercises/shared/exercise-headers/exercise-headers.module'; -import { ArtemisMarkdownModule } from 'app/shared/markdown.module'; -import { ExamExerciseOverviewPageComponent } from 'app/exam/participate/exercises/exercise-overview-page/exam-exercise-overview-page.component'; -import { SubmissionResultStatusModule } from 'app/overview/submission-result-status.module'; -import { ArtemisExamNavigationBarModule } from 'app/exam/participate/exam-navigation-bar/exam-navigation-bar.module'; -import { ArtemisExamTimerModule } from 'app/exam/participate/timer/exam-timer.module'; -import { ArtemisExamSubmissionComponentsModule } from 'app/exam/participate/exercises/exam-submission-components.module'; -import { ExamExerciseUpdateHighlighterModule } from 'app/exam/participate/exercises/exam-exercise-update-highlighter/exam-exercise-update-highlighter.module'; -import { ArtemisExamSharedModule } from 'app/exam/shared/exam-shared.module'; -import { ArtemisExamLiveEventsModule } from 'app/exam/participate/events/exam-live-events.module'; -import { ExamStartInformationComponent } from 'app/exam/participate/exam-start-information/exam-start-information.component'; -import { ArtemisSidebarModule } from 'app/shared/sidebar/sidebar.module'; -import { ExamNavigationSidebarComponent } from 'app/exam/participate/exam-navigation-sidebar/exam-navigation-sidebar.component'; -import { ExamBarComponent } from 'app/exam/participate/exam-bar/exam-bar.component'; -import { TestRunRibbonComponent } from 'app/exam/manage/test-runs/test-run-ribbon.component'; - -const ENTITY_STATES = [...examParticipationState]; - -@NgModule({ - imports: [ - RouterModule.forChild(ENTITY_STATES), - ArtemisSharedCommonModule, - ArtemisHeaderExercisePageWithDetailsModule, - ArtemisSharedModule, - ArtemisModelingEditorModule, - ArtemisQuizQuestionTypesModule, - ArtemisFullscreenModule, - ArtemisSharedComponentModule, - ArtemisProgrammingParticipationModule, - ArtemisCodeEditorModule, - ArtemisResultModule, - ArtemisProgrammingExerciseActionsModule, - ArtemisExerciseButtonsModule, - ArtemisProgrammingAssessmentModule, - ArtemisParticipationSummaryModule, - ArtemisMarkdownModule, - SubmissionResultStatusModule, - ArtemisExamNavigationBarModule, - ArtemisExamTimerModule, - ArtemisExamSubmissionComponentsModule, - ExamExerciseUpdateHighlighterModule, - ArtemisExamSharedModule, - ArtemisExamLiveEventsModule, - ExamStartInformationComponent, - ArtemisSidebarModule, - ExamNavigationSidebarComponent, - ExamBarComponent, - ExamParticipationComponent, - ExamParticipationCoverComponent, - ExamExerciseOverviewPageComponent, - TestRunRibbonComponent, - ], -}) -export class ArtemisExamParticipationModule {} diff --git a/src/main/webapp/app/exam/participate/exam-participation.route.ts b/src/main/webapp/app/exam/participate/exam-participation.route.ts index dfced7d039e0..6debe5bf4e5b 100644 --- a/src/main/webapp/app/exam/participate/exam-participation.route.ts +++ b/src/main/webapp/app/exam/participate/exam-participation.route.ts @@ -53,12 +53,3 @@ export const examParticipationRoute: Routes = [ canActivate: [UserRouteAccessService], }, ]; - -const EXAM_PARTICIPATION_ROUTES = [...examParticipationRoute]; - -export const examParticipationState: Routes = [ - { - path: '', - children: EXAM_PARTICIPATION_ROUTES, - }, -]; diff --git a/src/main/webapp/app/exam/participate/exam-start-information/exam-start-information.component.ts b/src/main/webapp/app/exam/participate/exam-start-information/exam-start-information.component.ts index 6e56677edc85..b0539ddd97c3 100644 --- a/src/main/webapp/app/exam/participate/exam-start-information/exam-start-information.component.ts +++ b/src/main/webapp/app/exam/participate/exam-start-information/exam-start-information.component.ts @@ -4,13 +4,13 @@ import { ArtemisSharedComponentModule } from 'app/shared/components/shared-compo import { InformationBox, InformationBoxComponent, InformationBoxContent } from 'app/shared/information-box/information-box.component'; import { Exam } from 'app/entities/exam/exam.model'; import { StudentExam } from 'app/entities/student-exam.model'; -import { ArtemisExamSharedModule } from 'app/exam/shared/exam-shared.module'; import dayjs from 'dayjs/esm'; import { SafeHtml } from '@angular/platform-browser'; +import { StudentExamWorkingTimeComponent } from 'app/exam/shared/student-exam-working-time/student-exam-working-time.component'; @Component({ selector: 'jhi-exam-start-information', - imports: [ArtemisSharedModule, ArtemisSharedComponentModule, InformationBoxComponent, ArtemisExamSharedModule], + imports: [ArtemisSharedModule, ArtemisSharedComponentModule, InformationBoxComponent, StudentExamWorkingTimeComponent], templateUrl: './exam-start-information.component.html', }) export class ExamStartInformationComponent implements OnInit { diff --git a/src/main/webapp/app/exam/participate/exercises/exam-exercise-update-highlighter/exam-exercise-update-highlighter.module.ts b/src/main/webapp/app/exam/participate/exercises/exam-exercise-update-highlighter/exam-exercise-update-highlighter.module.ts deleted file mode 100644 index e915682f2001..000000000000 --- a/src/main/webapp/app/exam/participate/exercises/exam-exercise-update-highlighter/exam-exercise-update-highlighter.module.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { NgModule } from '@angular/core'; -import { ArtemisSharedCommonModule } from 'app/shared/shared-common.module'; -import { ExamExerciseUpdateHighlighterComponent } from 'app/exam/participate/exercises/exam-exercise-update-highlighter/exam-exercise-update-highlighter.component'; - -@NgModule({ - imports: [ArtemisSharedCommonModule, ExamExerciseUpdateHighlighterComponent], - exports: [ExamExerciseUpdateHighlighterComponent], -}) -export class ExamExerciseUpdateHighlighterModule {} diff --git a/src/main/webapp/app/exam/participate/exercises/exam-submission-components.module.ts b/src/main/webapp/app/exam/participate/exercises/exam-submission-components.module.ts deleted file mode 100644 index 5fbbb9aee4bc..000000000000 --- a/src/main/webapp/app/exam/participate/exercises/exam-submission-components.module.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { FileUploadExamSubmissionComponent } from 'app/exam/participate/exercises/file-upload/file-upload-exam-submission.component'; -import { QuizExamSubmissionComponent } from 'app/exam/participate/exercises/quiz/quiz-exam-submission.component'; -import { ProgrammingExamSubmissionComponent } from 'app/exam/participate/exercises/programming/programming-exam-submission.component'; -import { TextExamSubmissionComponent } from 'app/exam/participate/exercises/text/text-exam-submission.component'; -import { ModelingExamSubmissionComponent } from 'app/exam/participate/exercises/modeling/modeling-exam-submission.component'; -import { ArtemisSharedModule } from 'app/shared/shared.module'; -import { ArtemisMarkdownModule } from 'app/shared/markdown.module'; -import { ExamExerciseUpdateHighlighterModule } from 'app/exam/participate/exercises/exam-exercise-update-highlighter/exam-exercise-update-highlighter.module'; -import { ArtemisSharedComponentModule } from 'app/shared/components/shared-component.module'; -import { ArtemisQuizQuestionTypesModule } from 'app/exercises/quiz/shared/questions/artemis-quiz-question-types.module'; -import { SubmissionResultStatusModule } from 'app/overview/submission-result-status.module'; -import { ArtemisExerciseButtonsModule } from 'app/overview/exercise-details/exercise-buttons.module'; -import { ArtemisProgrammingExerciseActionsModule } from 'app/exercises/programming/shared/actions/programming-exercise-actions.module'; -import { ArtemisCodeEditorModule } from 'app/exercises/programming/shared/code-editor/code-editor.module'; -import { ArtemisFullscreenModule } from 'app/shared/fullscreen/fullscreen.module'; -import { ArtemisModelingEditorModule } from 'app/exercises/modeling/shared/modeling-editor.module'; -import { ArtemisProgrammingSubmissionPolicyStatusModule } from 'app/exercises/programming/participate/programming-submission-policy-status.module'; -import { ExerciseSaveButtonComponent } from './exercise-save-button/exercise-save-button.component'; - -@NgModule({ - imports: [ - CommonModule, - ArtemisSharedModule, - ArtemisMarkdownModule, - ArtemisSharedComponentModule, - ArtemisQuizQuestionTypesModule, - SubmissionResultStatusModule, - ArtemisExerciseButtonsModule, - ArtemisProgrammingExerciseActionsModule, - ArtemisCodeEditorModule, - ArtemisFullscreenModule, - ArtemisModelingEditorModule, - ArtemisProgrammingSubmissionPolicyStatusModule, - ExamExerciseUpdateHighlighterModule, - ExerciseSaveButtonComponent, - FileUploadExamSubmissionComponent, - QuizExamSubmissionComponent, - ProgrammingExamSubmissionComponent, - TextExamSubmissionComponent, - ModelingExamSubmissionComponent, - ], - exports: [FileUploadExamSubmissionComponent, QuizExamSubmissionComponent, ProgrammingExamSubmissionComponent, TextExamSubmissionComponent, ModelingExamSubmissionComponent], -}) -export class ArtemisExamSubmissionComponentsModule {} diff --git a/src/main/webapp/app/exam/participate/summary/exam-result-summary.component.ts b/src/main/webapp/app/exam/participate/summary/exam-result-summary.component.ts index 3f2988b91922..f50d7b278a6e 100644 --- a/src/main/webapp/app/exam/participate/summary/exam-result-summary.component.ts +++ b/src/main/webapp/app/exam/participate/summary/exam-result-summary.component.ts @@ -185,7 +185,7 @@ export class ExamResultSummaryComponent implements OnInit { this.isTestExam = this.studentExam.exam!.testExam!; this.testRunConduction = this.isTestRun && this.route.snapshot.url[3]?.toString() === 'conduction'; this.testExamConduction = this.isTestExam && !this.studentExam.submitted; - this.courseId = Number(this.route.parent?.parent?.snapshot.paramMap.get('courseId')); + this.courseId = Number(this.route.snapshot?.paramMap?.get('courseId') || this.route.parent?.parent?.snapshot.paramMap.get('courseId')); if (!this.studentExam?.id) { throw new Error('studentExam.id should be present to fetch grade info'); } diff --git a/src/main/webapp/app/exam/participate/summary/exam-result-summary.module.ts b/src/main/webapp/app/exam/participate/summary/exam-result-summary.module.ts deleted file mode 100644 index f9c47c383be0..000000000000 --- a/src/main/webapp/app/exam/participate/summary/exam-result-summary.module.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { NgModule } from '@angular/core'; -import { RouterModule } from '@angular/router'; -import { ExamResultSummaryComponent } from 'app/exam/participate/summary/exam-result-summary.component'; -import { ProgrammingExamSummaryComponent } from 'app/exam/participate/summary/exercises/programming-exam-summary/programming-exam-summary.component'; -import { ModelingExamSummaryComponent } from 'app/exam/participate/summary/exercises/modeling-exam-summary/modeling-exam-summary.component'; -import { FileUploadExamSummaryComponent } from 'app/exam/participate/summary/exercises/file-upload-exam-summary/file-upload-exam-summary.component'; -import { TextExamSummaryComponent } from 'app/exam/participate/summary/exercises/text-exam-summary/text-exam-summary.component'; -import { QuizExamSummaryComponent } from 'app/exam/participate/summary/exercises/quiz-exam-summary/quiz-exam-summary.component'; -import { ArtemisSharedCommonModule } from 'app/shared/shared-common.module'; -import { ArtemisSharedModule } from 'app/shared/shared.module'; -import { ArtemisQuizQuestionTypesModule } from 'app/exercises/quiz/shared/questions/artemis-quiz-question-types.module'; -import { ArtemisModelingEditorModule } from 'app/exercises/modeling/shared/modeling-editor.module'; -import { ArtemisFullscreenModule } from 'app/shared/fullscreen/fullscreen.module'; -import { ArtemisResultModule } from 'app/exercises/shared/result/result.module'; -import { ArtemisComplaintsModule } from 'app/complaints/complaints.module'; -import { ExamGeneralInformationComponent } from 'app/exam/participate/general-information/exam-general-information.component'; -import { ExamResultOverviewComponent } from 'app/exam/participate/summary/result-overview/exam-result-overview.component'; -import { ArtemisHeaderExercisePageWithDetailsModule } from 'app/exercises/shared/exercise-headers/exercise-headers.module'; -import { ArtemisMarkdownModule } from 'app/shared/markdown.module'; -import { SubmissionResultStatusModule } from 'app/overview/submission-result-status.module'; -import { ArtemisExamSharedModule } from 'app/exam/shared/exam-shared.module'; -import { ExampleSolutionComponent } from 'app/exercises/shared/example-solution/example-solution.component'; -import { ArtemisProgrammingExerciseManagementModule } from 'app/exercises/programming/manage/programming-exercise-management.module'; -import { ArtemisSharedComponentModule } from 'app/shared/components/shared-component.module'; -import { GradingKeyOverviewModule } from 'app/grading-system/grading-key-overview/grading-key-overview.module'; -import { ExamResultSummaryExerciseCardHeaderComponent } from 'app/exam/participate/summary/exercises/header/exam-result-summary-exercise-card-header.component'; -import { ArtemisModelingParticipationModule } from 'app/exercises/modeling/participate/modeling-participation.module'; -import { ArtemisTextParticipationModule } from 'app/exercises/text/participate/text-participation.module'; -import { ArtemisFileUploadParticipationModule } from 'app/exercises/file-upload/participate/file-upload-participation.module'; -import { ArtemisFeedbackModule } from 'app/exercises/shared/feedback/feedback.module'; -import { CollapsibleCardComponent } from 'app/exam/participate/summary/collapsible-card.component'; -import { NoDataComponent } from 'app/shared/no-data-component'; - -@NgModule({ - imports: [ - RouterModule, - ArtemisSharedCommonModule, - ArtemisSharedModule, - ArtemisSharedComponentModule, - ArtemisQuizQuestionTypesModule, - ArtemisModelingEditorModule, - ArtemisFullscreenModule, - ArtemisResultModule, - ArtemisComplaintsModule, - ArtemisProgrammingExerciseManagementModule, - ArtemisHeaderExercisePageWithDetailsModule, - ArtemisMarkdownModule, - SubmissionResultStatusModule, - ArtemisExamSharedModule, - ArtemisSharedComponentModule, - GradingKeyOverviewModule, - ArtemisModelingParticipationModule, - ArtemisTextParticipationModule, - ArtemisFileUploadParticipationModule, - ArtemisFeedbackModule, - NoDataComponent, - ExamResultSummaryComponent, - ProgrammingExamSummaryComponent, - ModelingExamSummaryComponent, - FileUploadExamSummaryComponent, - TextExamSummaryComponent, - QuizExamSummaryComponent, - ExamGeneralInformationComponent, - ExamResultOverviewComponent, - ExamResultSummaryExerciseCardHeaderComponent, - ExampleSolutionComponent, - CollapsibleCardComponent, - ], - exports: [ExamResultSummaryComponent, ExamGeneralInformationComponent], -}) -export class ArtemisParticipationSummaryModule {} diff --git a/src/main/webapp/app/exam/participate/timer/exam-timer.module.ts b/src/main/webapp/app/exam/participate/timer/exam-timer.module.ts deleted file mode 100644 index 94c53564d044..000000000000 --- a/src/main/webapp/app/exam/participate/timer/exam-timer.module.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { ExamTimerComponent } from 'app/exam/participate/timer/exam-timer.component'; -import { ArtemisSharedCommonModule } from 'app/shared/shared-common.module'; - -@NgModule({ - imports: [CommonModule, ArtemisSharedCommonModule, ExamTimerComponent], - exports: [ExamTimerComponent], -}) -export class ArtemisExamTimerModule {} diff --git a/src/main/webapp/app/exam/shared/exam-shared.module.ts b/src/main/webapp/app/exam/shared/exam-shared.module.ts deleted file mode 100644 index 68d31244d222..000000000000 --- a/src/main/webapp/app/exam/shared/exam-shared.module.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { NgModule } from '@angular/core'; -import { StudentExamWorkingTimeComponent } from 'app/exam/shared/student-exam-working-time/student-exam-working-time.component'; -import { ArtemisSharedCommonModule } from 'app/shared/shared-common.module'; -import { TestExamWorkingTimeComponent } from 'app/exam/shared/testExam-workingTime/test-exam-working-time.component'; -import { WorkingTimeControlComponent } from 'app/exam/shared/working-time-control/working-time-control.component'; -import { ExamLiveEventComponent } from 'app/exam/shared/events/exam-live-event.component'; -import { ArtemisMarkdownModule } from 'app/shared/markdown.module'; -import { WorkingTimeChangeComponent } from 'app/exam/shared/working-time-change/working-time-change.component'; - -@NgModule({ - imports: [ - ArtemisSharedCommonModule, - ArtemisMarkdownModule, - StudentExamWorkingTimeComponent, - TestExamWorkingTimeComponent, - WorkingTimeControlComponent, - WorkingTimeChangeComponent, - ExamLiveEventComponent, - ], - exports: [StudentExamWorkingTimeComponent, TestExamWorkingTimeComponent, WorkingTimeControlComponent, WorkingTimeChangeComponent, ExamLiveEventComponent], -}) -export class ArtemisExamSharedModule {} diff --git a/src/main/webapp/app/exercises/programming/manage/programming-exercise-detail.component.ts b/src/main/webapp/app/exercises/programming/manage/programming-exercise-detail.component.ts index c3eff649807f..0cbb2cffaff5 100644 --- a/src/main/webapp/app/exercises/programming/manage/programming-exercise-detail.component.ts +++ b/src/main/webapp/app/exercises/programming/manage/programming-exercise-detail.component.ts @@ -230,7 +230,7 @@ export class ProgrammingExerciseDetailComponent implements OnInit, OnDestroy { ); } this.supportsAuxiliaryRepositories = - this.programmingLanguageFeatureService.getProgrammingLanguageFeature(programmingExercise.programmingLanguage).auxiliaryRepositoriesSupported ?? + this.programmingLanguageFeatureService.getProgrammingLanguageFeature(programmingExercise.programmingLanguage)?.auxiliaryRepositoriesSupported ?? false; this.localVCEnabled = profileInfo.activeProfiles.includes(PROFILE_LOCALVC); this.localCIEnabled = profileInfo.activeProfiles.includes(PROFILE_LOCALCI); @@ -269,9 +269,8 @@ export class ProgrammingExerciseDetailComponent implements OnInit, OnDestroy { .subscribe({ next: () => { this.checkAndAlertInconsistencies(); - this.plagiarismCheckSupported = this.programmingLanguageFeatureService.getProgrammingLanguageFeature( - programmingExercise.programmingLanguage, - ).plagiarismCheckSupported; + this.plagiarismCheckSupported = + this.programmingLanguageFeatureService.getProgrammingLanguageFeature(programmingExercise.programmingLanguage)?.plagiarismCheckSupported ?? false; /** we make sure to await the results of the subscriptions (switchMap) to only call {@link getExerciseDetails} once */ this.exerciseDetailSections = this.getExerciseDetails(); diff --git a/src/main/webapp/app/exercises/programming/manage/update/programming-exercise-update.component.ts b/src/main/webapp/app/exercises/programming/manage/update/programming-exercise-update.component.ts index 732a969f49b0..6dfa8ae91c74 100644 --- a/src/main/webapp/app/exercises/programming/manage/update/programming-exercise-update.component.ts +++ b/src/main/webapp/app/exercises/programming/manage/update/programming-exercise-update.component.ts @@ -289,7 +289,7 @@ export class ProgrammingExerciseUpdateComponent implements AfterViewInit, OnDest const languageChanged = this.selectedProgrammingLanguageValue !== language; this.selectedProgrammingLanguageValue = language; - const programmingLanguageFeature = this.programmingLanguageFeatureService.getProgrammingLanguageFeature(language); + const programmingLanguageFeature = this.programmingLanguageFeatureService.getProgrammingLanguageFeature(language)!; this.packageNameRequired = programmingLanguageFeature?.packageNameRequired; this.staticCodeAnalysisAllowed = programmingLanguageFeature.staticCodeAnalysis; this.checkoutSolutionRepositoryAllowed = programmingLanguageFeature.checkoutSolutionRepositoryAllowed; @@ -377,7 +377,7 @@ export class ProgrammingExerciseUpdateComponent implements AfterViewInit, OnDest // update the project types for java programming exercises according to whether dependencies should be included if (this.programmingExercise.programmingLanguage === ProgrammingLanguage.JAVA) { - const programmingLanguageFeature = this.programmingLanguageFeatureService.getProgrammingLanguageFeature(ProgrammingLanguage.JAVA); + const programmingLanguageFeature = this.programmingLanguageFeatureService.getProgrammingLanguageFeature(ProgrammingLanguage.JAVA)!; if (type == ProjectType.MAVEN_BLACKBOX) { this.selectedProjectTypeValue = ProjectType.MAVEN_BLACKBOX; this.programmingExercise.projectType = ProjectType.MAVEN_BLACKBOX; diff --git a/src/main/webapp/app/exercises/programming/shared/service/programming-language-feature/programming-language-feature.service.ts b/src/main/webapp/app/exercises/programming/shared/service/programming-language-feature/programming-language-feature.service.ts index 9188c2afaea0..8fca224ebfa7 100644 --- a/src/main/webapp/app/exercises/programming/shared/service/programming-language-feature/programming-language-feature.service.ts +++ b/src/main/webapp/app/exercises/programming/shared/service/programming-language-feature/programming-language-feature.service.ts @@ -31,8 +31,8 @@ export class ProgrammingLanguageFeatureService { }); } - public getProgrammingLanguageFeature(programmingLanguage: ProgrammingLanguage): ProgrammingLanguageFeature { - return this.programmingLanguageFeatures.get(programmingLanguage)!; + public getProgrammingLanguageFeature(programmingLanguage: ProgrammingLanguage): ProgrammingLanguageFeature | undefined { + return this.programmingLanguageFeatures.get(programmingLanguage); } public supportsProgrammingLanguage(programmingLanguage: ProgrammingLanguage): boolean { diff --git a/src/main/webapp/app/overview/course-exams/course-exams.module.ts b/src/main/webapp/app/overview/course-exams/course-exams.module.ts deleted file mode 100644 index 69d09384d273..000000000000 --- a/src/main/webapp/app/overview/course-exams/course-exams.module.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { ArtemisSharedModule } from 'app/shared/shared.module'; -import { CourseExamsComponent } from 'app/overview/course-exams/course-exams.component'; -import { ArtemisExamSharedModule } from 'app/exam/shared/exam-shared.module'; -import { NgModule } from '@angular/core'; -import { ArtemisSharedCommonModule } from 'app/shared/shared-common.module'; -import { ArtemisSharedComponentModule } from 'app/shared/components/shared-component.module'; -import { ArtemisSidebarModule } from 'app/shared/sidebar/sidebar.module'; - -@NgModule({ - imports: [ArtemisSharedModule, ArtemisSharedCommonModule, ArtemisSharedComponentModule, ArtemisExamSharedModule, ArtemisSidebarModule, CourseExamsComponent], - exports: [CourseExamsComponent], -}) -export class CourseExamsModule {} diff --git a/src/main/webapp/app/overview/courses-routing.module.ts b/src/main/webapp/app/overview/courses-routing.module.ts index 0669891b2db4..79171c01970b 100644 --- a/src/main/webapp/app/overview/courses-routing.module.ts +++ b/src/main/webapp/app/overview/courses-routing.module.ts @@ -269,7 +269,7 @@ const routes: Routes = [ }, canActivate: [UserRouteAccessService], canDeactivate: [PendingChangesGuard], - loadChildren: () => import('../exam/participate/exam-participation.module').then((m) => m.ArtemisExamParticipationModule), + loadChildren: () => import('../exam/participate/exam-participation.route').then((m) => m.examParticipationRoute), }, ], }, diff --git a/src/main/webapp/app/shared/sidebar/sidebar.module.ts b/src/main/webapp/app/shared/sidebar/sidebar.module.ts index 8f46f0f921c3..24a81c5ff50f 100644 --- a/src/main/webapp/app/shared/sidebar/sidebar.module.ts +++ b/src/main/webapp/app/shared/sidebar/sidebar.module.ts @@ -13,7 +13,6 @@ import { ArtemisSharedCommonModule } from '../shared-common.module'; import { SubmissionResultStatusModule } from 'app/overview/submission-result-status.module'; import { SidebarCardDirective } from 'app/shared/sidebar/sidebar-card.directive'; import { ConversationOptionsComponent } from 'app/shared/sidebar/conversation-options/conversation-options.component'; -import { ArtemisExamSharedModule } from 'app/exam/shared/exam-shared.module'; import { SearchFilterComponent } from 'app/shared/search-filter/search-filter.component'; import { ProfilePictureComponent } from 'app/shared/profile-picture/profile-picture.component'; @@ -26,7 +25,6 @@ import { ProfilePictureComponent } from 'app/shared/profile-picture/profile-pict ArtemisSharedCommonModule, SubmissionResultStatusModule, SidebarCardDirective, - ArtemisExamSharedModule, SearchFilterComponent, ProfilePictureComponent, SidebarAccordionComponent, diff --git a/src/test/java/de/tum/cit/aet/artemis/core/config/LicenseConfigurationTest.java b/src/test/java/de/tum/cit/aet/artemis/core/config/LicenseConfigurationTest.java new file mode 100644 index 000000000000..d6ea2691f4d2 --- /dev/null +++ b/src/test/java/de/tum/cit/aet/artemis/core/config/LicenseConfigurationTest.java @@ -0,0 +1,27 @@ +package de.tum.cit.aet.artemis.core.config; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; + +class LicenseConfigurationTest { + + @Test + void testMatlabLicenseServer() { + String licenseServer = "1234@license-server"; + LicenseConfiguration licenseConfiguration = new LicenseConfiguration(new LicenseConfiguration.MatLabLicense(licenseServer)); + assertThat(licenseConfiguration.getMatlabLicenseServer()).isEqualTo(licenseServer); + } + + @Test + void testMatlabNullRecord() { + LicenseConfiguration licenseConfiguration = new LicenseConfiguration(null); + assertThat(licenseConfiguration.getMatlabLicenseServer()).isNull(); + } + + @Test + void testMatlabNullValue() { + LicenseConfiguration licenseConfiguration = new LicenseConfiguration(new LicenseConfiguration.MatLabLicense(null)); + assertThat(licenseConfiguration.getMatlabLicenseServer()).isNull(); + } +} diff --git a/src/test/java/de/tum/cit/aet/artemis/programming/service/LicenseServiceTest.java b/src/test/java/de/tum/cit/aet/artemis/programming/service/LicenseServiceTest.java new file mode 100644 index 000000000000..86621e4042d8 --- /dev/null +++ b/src/test/java/de/tum/cit/aet/artemis/programming/service/LicenseServiceTest.java @@ -0,0 +1,51 @@ +package de.tum.cit.aet.artemis.programming.service; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Map; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import de.tum.cit.aet.artemis.core.config.LicenseConfiguration; +import de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage; +import de.tum.cit.aet.artemis.programming.domain.ProjectType; +import de.tum.cit.aet.artemis.shared.base.AbstractSpringIntegrationIndependentTest; + +class LicenseServiceTest extends AbstractSpringIntegrationIndependentTest { + + @Autowired + private LicenseService licenseService; + + @Test + void testIsLicensedNoneRequired() { + boolean isLicensed = licenseService.isLicensed(ProgrammingLanguage.JAVA, ProjectType.GRADLE_GRADLE); + assertThat(isLicensed).isTrue(); + } + + @Test + void testGetLicenseNoneRequired() { + Map environment = licenseService.getEnvironment(ProgrammingLanguage.JAVA, ProjectType.GRADLE_GRADLE); + assertThat(environment).isEmpty(); + } + + @Test + void testIsLicensedMatlab() { + boolean isLicensed = licenseService.isLicensed(ProgrammingLanguage.MATLAB, null); + assertThat(isLicensed).isTrue(); + } + + @Test + void testGetLicenseMatlab() { + Map environment = licenseService.getEnvironment(ProgrammingLanguage.MATLAB, null); + assertThat(environment).containsEntry("MLM_LICENSE_FILE", "1234@license-server"); + } + + @Test + void testIsLicensedMatlabUnlicensed() { + LicenseConfiguration licenseConfiguration = new LicenseConfiguration(null); + LicenseService licenseService = new LicenseService(licenseConfiguration); + boolean isLicensed = licenseService.isLicensed(ProgrammingLanguage.MATLAB, null); + assertThat(isLicensed).isFalse(); + } +} diff --git a/src/test/java/de/tum/cit/aet/artemis/programming/util/ArgumentSources.java b/src/test/java/de/tum/cit/aet/artemis/programming/util/ArgumentSources.java index c8b66beb167f..f7c94236f82b 100644 --- a/src/test/java/de/tum/cit/aet/artemis/programming/util/ArgumentSources.java +++ b/src/test/java/de/tum/cit/aet/artemis/programming/util/ArgumentSources.java @@ -1,6 +1,7 @@ package de.tum.cit.aet.artemis.programming.util; import static de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage.ASSEMBLER; +import static de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage.MATLAB; import static de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage.OCAML; import static de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage.VHDL; @@ -14,7 +15,7 @@ public class ArgumentSources { // TODO: Add template for VHDL, Assembler, and Ocaml and activate those languages here again public static Set generateJenkinsSupportedLanguages() { - List unsupportedLanguages = List.of(VHDL, ASSEMBLER, OCAML); + List unsupportedLanguages = List.of(VHDL, ASSEMBLER, OCAML, MATLAB); var supportedLanguages = EnumSet.copyOf(ProgrammingLanguage.getEnabledLanguages()); unsupportedLanguages.forEach(supportedLanguages::remove); diff --git a/src/test/java/de/tum/cit/aet/artemis/shared/base/AbstractSpringIntegrationLocalCILocalVCTest.java b/src/test/java/de/tum/cit/aet/artemis/shared/base/AbstractSpringIntegrationLocalCILocalVCTest.java index 2538b01761f7..83333e3e6049 100644 --- a/src/test/java/de/tum/cit/aet/artemis/shared/base/AbstractSpringIntegrationLocalCILocalVCTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/shared/base/AbstractSpringIntegrationLocalCILocalVCTest.java @@ -245,7 +245,7 @@ protected static void mockDockerClient() throws InterruptedException { String dummyContainerId = "1234567890"; - // Mock dockerClient.createContainerCmd(String dockerImage).withHostConfig(HostConfig hostConfig).withEnv(String... env).withCmd(String... cmd).exec() + // Mock dockerClient.createContainerCmd(String dockerImage).withHostConfig(HostConfig hostConfig).withEnv(String... env).withEntrypoint().withCmd(String... cmd).exec() CreateContainerCmd createContainerCmd = mock(CreateContainerCmd.class); CreateContainerResponse createContainerResponse = new CreateContainerResponse(); createContainerResponse.setId(dummyContainerId); @@ -254,6 +254,7 @@ protected static void mockDockerClient() throws InterruptedException { doReturn(createContainerCmd).when(createContainerCmd).withHostConfig(any()); doReturn(createContainerCmd).when(createContainerCmd).withEnv(anyList()); doReturn(createContainerCmd).when(createContainerCmd).withUser(anyString()); + doReturn(createContainerCmd).when(createContainerCmd).withEntrypoint(); doReturn(createContainerCmd).when(createContainerCmd).withCmd(anyString(), anyString(), anyString()); doReturn(createContainerResponse).when(createContainerCmd).exec(); diff --git a/src/test/javascript/spec/component/exam/participate/exam-start-information/exam-start-information.component.spec.ts b/src/test/javascript/spec/component/exam/participate/exam-start-information/exam-start-information.component.spec.ts index c675ea69d8c7..6cf5e3ef6db6 100644 --- a/src/test/javascript/spec/component/exam/participate/exam-start-information/exam-start-information.component.spec.ts +++ b/src/test/javascript/spec/component/exam/participate/exam-start-information/exam-start-information.component.spec.ts @@ -12,7 +12,6 @@ import dayjs from 'dayjs/esm'; import { MockComponent, MockDirective, MockPipe } from 'ng-mocks'; import { ArtemisSharedModule } from 'app/shared/shared.module'; import { ArtemisSharedComponentModule } from 'app/shared/components/shared-component.module'; -import { ArtemisExamSharedModule } from 'app/exam/shared/exam-shared.module'; import { TranslateDirective } from 'app/shared/language/translate.directive'; import { provideRouter } from '@angular/router'; @@ -40,7 +39,7 @@ describe('ExamStartInformationComponent', () => { studentExam = { id: 1, exam, user, workingTime: 60, submitted: true } as StudentExam; return TestBed.configureTestingModule({ - imports: [ArtemisSharedModule, ArtemisSharedComponentModule, ArtemisExamSharedModule], + imports: [ArtemisSharedModule, ArtemisSharedComponentModule], declarations: [ ExamStartInformationComponent, MockComponent(StudentExamWorkingTimeComponent), diff --git a/src/test/resources/config/application.yml b/src/test/resources/config/application.yml index 869514c89759..43996ae2d62b 100644 --- a/src/test/resources/config/application.yml +++ b/src/test/resources/config/application.yml @@ -82,11 +82,18 @@ artemis: default: "~~invalid~~" go: default: "~~invalid~~" + matlab: + default: "~~invalid~~" + ruby: + default: "~~invalid~~" build-agent: short-name: "artemis-build-agent-test" telemetry: enabled: false # setting this to false will disable sending any information to the telemetry service - + licenses: + matlab: + license-server: "1234@license-server" + spring: application: name: Artemis