Skip to content

Commit

Permalink
Merge branch 'develop' into feature/communication/send-faq-to-iris
Browse files Browse the repository at this point in the history
  • Loading branch information
cremertim authored Jan 31, 2025
2 parents e3153e7 + f7c5970 commit 33c7b2c
Show file tree
Hide file tree
Showing 139 changed files with 2,777 additions and 852 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions docs/user/exercises/programming-exercise-features.inc
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
6 changes: 3 additions & 3 deletions gradle/jacoco.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ ext {
"CLASS": 1
],
"iris" : [
"INSTRUCTION": 0.792,
"CLASS": 17
"INSTRUCTION": 0.775,
"CLASS": 22
],
"lecture" : [
"INSTRUCTION": 0.867,
Expand Down Expand Up @@ -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/**"
]
}
Expand Down
526 changes: 263 additions & 263 deletions package-lock.json

Large diffs are not rendered by default.

42 changes: 21 additions & 21 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,18 @@
"node_modules"
],
"dependencies": {
"@angular/animations": "19.1.3",
"@angular/cdk": "19.1.1",
"@angular/common": "19.1.3",
"@angular/compiler": "19.1.3",
"@angular/core": "19.1.3",
"@angular/forms": "19.1.3",
"@angular/localize": "19.1.3",
"@angular/material": "19.1.1",
"@angular/platform-browser": "19.1.3",
"@angular/platform-browser-dynamic": "19.1.3",
"@angular/router": "19.1.3",
"@angular/service-worker": "19.1.3",
"@angular/animations": "19.1.4",
"@angular/cdk": "19.1.2",
"@angular/common": "19.1.4",
"@angular/compiler": "19.1.4",
"@angular/core": "19.1.4",
"@angular/forms": "19.1.4",
"@angular/localize": "19.1.4",
"@angular/material": "19.1.2",
"@angular/platform-browser": "19.1.4",
"@angular/platform-browser-dynamic": "19.1.4",
"@angular/router": "19.1.4",
"@angular/service-worker": "19.1.4",
"@ctrl/ngx-emoji-mart": "9.2.0",
"@danielmoncada/angular-datetime-picker": "19.1.0",
"@fingerprintjs/fingerprintjs": "4.5.1",
Expand All @@ -36,7 +36,7 @@
"@ng-bootstrap/ng-bootstrap": "18.0.0",
"@ngx-translate/core": "16.0.4",
"@ngx-translate/http-loader": "16.0.1",
"@sentry/angular": "8.51.0",
"@sentry/angular": "8.52.1",
"@siemens/ngx-datatable": "22.4.1",
"@swimlane/ngx-charts": "21.1.3",
"@swimlane/ngx-graph": "9.0.1",
Expand All @@ -48,7 +48,7 @@
"crypto-js": "4.2.0",
"dayjs": "1.11.13",
"diff-match-patch-typescript": "1.1.0",
"dompurify": "3.2.3",
"dompurify": "3.2.4",
"emoji-js": "3.8.1",
"export-to-csv": "1.4.0",
"fast-json-patch": "3.1.1",
Expand All @@ -68,7 +68,7 @@
"ngx-infinite-scroll": "19.0.0",
"ngx-webstorage": "19.0.1",
"pako": "2.1.0",
"papaparse": "5.5.1",
"papaparse": "5.5.2",
"pdf-lib": "1.17.1",
"pdfjs-dist": "4.10.38",
"rxjs": "7.8.1",
Expand Down Expand Up @@ -138,11 +138,11 @@
"@angular-eslint/eslint-plugin-template": "19.0.2",
"@angular-eslint/schematics": "19.0.2",
"@angular-eslint/template-parser": "19.0.2",
"@angular/build": "19.1.4",
"@angular/cli": "19.1.4",
"@angular/compiler-cli": "19.1.3",
"@angular/language-service": "19.1.3",
"@sentry/types": "8.51.0",
"@angular/build": "19.1.5",
"@angular/cli": "19.1.5",
"@angular/compiler-cli": "19.1.4",
"@angular/language-service": "19.1.4",
"@sentry/types": "8.52.1",
"@testing-library/angular": "17.3.5",
"@types/crypto-js": "4.2.2",
"@types/d3-shape": "3.1.7",
Expand All @@ -151,7 +151,7 @@
"@types/jest": "29.5.14",
"@types/lodash-es": "4.17.12",
"@types/markdown-it": "14.1.2",
"@types/node": "22.10.10",
"@types/node": "22.12.0",
"@types/pako": "2.0.3",
"@types/papaparse": "5.3.15",
"@types/smoothscroll-polyfill": "0.3.4",
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/de/tum/cit/aet/artemis/ArtemisApp.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 <container-id> /bin/bash".
.exec();
Expand Down
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package de.tum.cit.aet.artemis.iris.service;

import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_IRIS;

import java.util.Optional;

import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;

import de.tum.cit.aet.artemis.core.domain.Course;
import de.tum.cit.aet.artemis.core.domain.LLMServiceType;
import de.tum.cit.aet.artemis.core.domain.User;
import de.tum.cit.aet.artemis.core.repository.UserRepository;
import de.tum.cit.aet.artemis.core.service.LLMTokenUsageService;
import de.tum.cit.aet.artemis.exercise.domain.Exercise;
import de.tum.cit.aet.artemis.exercise.repository.ExerciseRepository;
import de.tum.cit.aet.artemis.iris.service.pyris.PyrisDTOService;
import de.tum.cit.aet.artemis.iris.service.pyris.PyrisJobService;
import de.tum.cit.aet.artemis.iris.service.pyris.PyrisPipelineService;
import de.tum.cit.aet.artemis.iris.service.pyris.dto.consistencyCheck.PyrisConsistencyCheckPipelineExecutionDTO;
import de.tum.cit.aet.artemis.iris.service.pyris.dto.consistencyCheck.PyrisConsistencyCheckStatusUpdateDTO;
import de.tum.cit.aet.artemis.iris.service.pyris.job.ConsistencyCheckJob;
import de.tum.cit.aet.artemis.iris.service.websocket.IrisWebsocketService;
import de.tum.cit.aet.artemis.programming.domain.ProgrammingExercise;

/**
* Service to handle the rewriting subsystem of Iris.
*/
@Service
@Profile(PROFILE_IRIS)
public class IrisConsistencyCheckService {

private final PyrisPipelineService pyrisPipelineService;

private final LLMTokenUsageService llmTokenUsageService;

private final ExerciseRepository exerciseRepository;

private final IrisWebsocketService websocketService;

private final PyrisJobService pyrisJobService;

private final UserRepository userRepository;

private final PyrisDTOService pyrisDTOService;

public IrisConsistencyCheckService(PyrisPipelineService pyrisPipelineService, LLMTokenUsageService llmTokenUsageService, ExerciseRepository exerciseRepository,
IrisWebsocketService websocketService, PyrisJobService pyrisJobService, UserRepository userRepository, PyrisDTOService pyrisDTOService) {
this.pyrisPipelineService = pyrisPipelineService;
this.llmTokenUsageService = llmTokenUsageService;
this.exerciseRepository = exerciseRepository;
this.websocketService = websocketService;
this.pyrisJobService = pyrisJobService;
this.userRepository = userRepository;
this.pyrisDTOService = pyrisDTOService;
}

/**
* Executes the consistency check pipeline on Pyris
*
* @param user the user for which the pipeline should be executed
* @param exercise the exercise for which the pipeline should be executed
*/
public void executeConsistencyCheckPipeline(User user, ProgrammingExercise exercise) {
Course course = exercise.getCourseViaExerciseGroupOrCourseMember();
// @formatter:off
pyrisPipelineService.executePipeline(
"inconsistency-check",
"default",
Optional.empty(),
pyrisJobService.createTokenForJob(token -> new ConsistencyCheckJob(token, course.getId(), exercise.getId(), user.getId())),
executionDto -> new PyrisConsistencyCheckPipelineExecutionDTO(executionDto, pyrisDTOService.toPyrisProgrammingExerciseDTO(exercise)),
stages -> websocketService.send(user.getLogin(), websocketTopic(exercise.getId()), new PyrisConsistencyCheckStatusUpdateDTO(stages, null, null))
);
// @formatter:on
}

/**
* Takes a status update from Pyris containing a new consistency check result and sends it to the client via websocket
*
* @param job Job related to the status update
* @param statusUpdate the status update containing the consistency check result
* @return the same job that was passed in
*/
public ConsistencyCheckJob handleStatusUpdate(ConsistencyCheckJob job, PyrisConsistencyCheckStatusUpdateDTO statusUpdate) {
Exercise exercise = exerciseRepository.findByIdElseThrow(job.exerciseId());
if (statusUpdate.tokens() != null && !statusUpdate.tokens().isEmpty()) {
llmTokenUsageService.saveLLMTokenUsage(statusUpdate.tokens(), LLMServiceType.IRIS, builder -> builder.withExercise(exercise.getId()).withUser(job.userId()));
}

var user = userRepository.findById(job.userId()).orElseThrow();
websocketService.send(user.getLogin(), websocketTopic(job.exerciseId()), statusUpdate);

return job;
}

private static String websocketTopic(long exerciseId) {
return "consistency-check/exercises/" + exerciseId;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package de.tum.cit.aet.artemis.iris.service;

import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_IRIS;

import java.util.Optional;

import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;

import de.tum.cit.aet.artemis.core.domain.Course;
import de.tum.cit.aet.artemis.core.domain.LLMServiceType;
import de.tum.cit.aet.artemis.core.domain.User;
import de.tum.cit.aet.artemis.core.repository.CourseRepository;
import de.tum.cit.aet.artemis.core.repository.UserRepository;
import de.tum.cit.aet.artemis.core.service.LLMTokenUsageService;
import de.tum.cit.aet.artemis.iris.service.pyris.PyrisJobService;
import de.tum.cit.aet.artemis.iris.service.pyris.PyrisPipelineService;
import de.tum.cit.aet.artemis.iris.service.pyris.dto.rewriting.PyrisRewritingPipelineExecutionDTO;
import de.tum.cit.aet.artemis.iris.service.pyris.dto.rewriting.PyrisRewritingStatusUpdateDTO;
import de.tum.cit.aet.artemis.iris.service.pyris.dto.rewriting.RewritingVariant;
import de.tum.cit.aet.artemis.iris.service.pyris.job.RewritingJob;
import de.tum.cit.aet.artemis.iris.service.websocket.IrisWebsocketService;

/**
* Service to handle the rewriting subsystem of Iris.
*/
@Service
@Profile(PROFILE_IRIS)
public class IrisRewritingService {

private final PyrisPipelineService pyrisPipelineService;

private final LLMTokenUsageService llmTokenUsageService;

private final CourseRepository courseRepository;

private final IrisWebsocketService websocketService;

private final PyrisJobService pyrisJobService;

private final UserRepository userRepository;

public IrisRewritingService(PyrisPipelineService pyrisPipelineService, LLMTokenUsageService llmTokenUsageService, CourseRepository courseRepository,
IrisWebsocketService websocketService, PyrisJobService pyrisJobService, UserRepository userRepository) {
this.pyrisPipelineService = pyrisPipelineService;
this.llmTokenUsageService = llmTokenUsageService;
this.courseRepository = courseRepository;
this.websocketService = websocketService;
this.pyrisJobService = pyrisJobService;
this.userRepository = userRepository;
}

/**
* Executes the rewriting pipeline on Pyris
*
* @param user the user for which the pipeline should be executed
* @param course the course for which the pipeline should be executed
* @param variant the rewriting variant to be used
* @param toBeRewritten the text to be rewritten
*/
public void executeRewritingPipeline(User user, Course course, RewritingVariant variant, String toBeRewritten) {
// @formatter:off
pyrisPipelineService.executePipeline(
"rewriting",
variant.toString(),
Optional.empty(),
pyrisJobService.createTokenForJob(token -> new RewritingJob(token, course.getId(), user.getId())),
executionDto -> new PyrisRewritingPipelineExecutionDTO(executionDto, toBeRewritten),
stages -> websocketService.send(user.getLogin(), websocketTopic(course.getId()), new PyrisRewritingStatusUpdateDTO(stages, null, null))
);
// @formatter:on
}

/**
* Takes a status update from Pyris containing a new rewriting result and sends it to the client via websocket
*
* @param job Job related to the status update
* @param statusUpdate the status update containing text recommendations
* @return the same job that was passed in
*/
public RewritingJob handleStatusUpdate(RewritingJob job, PyrisRewritingStatusUpdateDTO statusUpdate) {
Course course = courseRepository.findByIdForUpdateElseThrow(job.courseId());
if (statusUpdate.tokens() != null && !statusUpdate.tokens().isEmpty()) {
llmTokenUsageService.saveLLMTokenUsage(statusUpdate.tokens(), LLMServiceType.IRIS, builder -> builder.withCourse(course.getId()).withUser(job.userId()));
}

var user = userRepository.findById(job.userId()).orElseThrow();
websocketService.send(user.getLogin(), websocketTopic(job.courseId()), statusUpdate);

return job;
}

private static String websocketTopic(long courseId) {
return "rewriting/" + courseId;
}

}
Loading

0 comments on commit 33c7b2c

Please sign in to comment.