Skip to content

Commit 5855e7d

Browse files
authored
Merge branch 'develop' into feature/programming-exercises/choose-preliminary-feedback-model
2 parents 9e86e4a + 45d9b33 commit 5855e7d

File tree

71 files changed

+2852
-1949
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

71 files changed

+2852
-1949
lines changed

build.gradle

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ plugins {
2121
id "com.google.cloud.tools.jib" version "3.4.5"
2222
id "com.gorylenko.gradle-git-properties" version "2.5.0"
2323
id "io.spring.dependency-management" version "1.1.7"
24-
id "nebula.lint" version "20.5.8"
24+
id "nebula.lint" version "20.6.0"
2525
id "org.liquibase.gradle" version "${liquibase_plugin_version}"
2626
id "org.owasp.dependencycheck" version "12.1.1"
2727
id "org.springframework.boot" version "${spring_boot_version}"
@@ -269,7 +269,7 @@ dependencies {
269269
implementation "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:${fasterxml_version}"
270270

271271
// Avoid outdated version of netty to prevent security issues
272-
implementation("net.minidev:json-smart") { version { strictly "2.5.2" } }
272+
implementation("net.minidev:json-smart") { version {strictly "2.5.2" } }
273273

274274

275275
// Required for synchronization between nodes and build agents (LocalCI)
@@ -316,7 +316,7 @@ dependencies {
316316
implementation "org.springframework.ldap:spring-ldap-core:3.3.0"
317317

318318
implementation "org.springframework.cloud:spring-cloud-starter-netflix-eureka-client:${spring_cloud_version}"
319-
implementation "org.springframework.cloud:spring-cloud-starter-config:4.2.2"
319+
implementation "org.springframework.cloud:spring-cloud-starter-config:${spring_cloud_version}"
320320
implementation "org.springframework.cloud:spring-cloud-commons:${spring_cloud_version}"
321321

322322
// required by the Websocket Broker Connection in WebsocketConfiguration (due to multi node setup support)
@@ -359,7 +359,7 @@ dependencies {
359359
implementation "org.bouncycastle:bcprov-jdk18on:1.80"
360360

361361
implementation "com.mysql:mysql-connector-j:${mysql_version}"
362-
implementation "org.postgresql:postgresql:42.7.5"
362+
implementation "org.postgresql:postgresql:42.7.6"
363363

364364
implementation "org.zalando:problem-spring-web:0.29.1"
365365
implementation "org.zalando:jackson-datatype-problem:0.27.1"

gradle.properties

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ npm_version=10.9.2
77

88
# Dependency versions
99
jhipster_dependencies_version=8.11.0
10-
spring_boot_version=3.4.6
10+
spring_boot_version=3.5.0
1111
spring_framework_version=6.2.7
12-
spring_cloud_version=4.2.1
12+
spring_cloud_version=4.3.0
1313
spring_security_version=6.5.0
1414
# TODO: upgrading to 6.6.x currently leads to issues due to internal changes in Hibernate and potentially wrong use in Artemis server code. See https://hibernate.atlassian.net/browse/HHH-19249
1515
hibernate_version=6.5.3.Final
@@ -26,7 +26,7 @@ jplag_version=6.1.0
2626
# NOTE: we cannot need to use the latest version 9.x or 10.x here as long as Stanford CoreNLP does not reference it
2727
lucene_version=8.11.4
2828
slf4j_version=2.0.17
29-
sentry_version=8.12.0
29+
sentry_version=8.13.2
3030
liquibase_version=4.32.0
3131
docker_java_version=3.5.1
3232
logback_version=1.5.18
@@ -38,8 +38,8 @@ micrometer_version=1.15.0
3838

3939
# testing
4040
# make sure both versions are compatible
41-
junit_version=5.12.2
42-
junit_platform_version=1.12.2
41+
junit_version=5.13.0
42+
junit_platform_version=1.13.0
4343
mockito_version=5.18.0
4444
testcontainer_version=1.20.4
4545

@@ -48,7 +48,7 @@ gradle_node_plugin_version=7.1.0
4848
apt_plugin_version=0.21
4949
liquibase_plugin_version=3.0.2
5050
modernizer_plugin_version=1.11.0
51-
spotless_plugin_version=7.0.3
51+
spotless_plugin_version=7.0.4
5252

5353
org.gradle.jvmargs=-Xmx2g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -Duser.country=US -Duser.language=en \
5454
--add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED \

jest.config.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,8 @@ module.exports = {
100100
lines: 89.22,
101101
},
102102
},
103-
coverageReporters: ['clover', 'json', 'lcov', 'text-summary'],
103+
// 'json-summary' reporter is used by supporting_scripts/code-coverage/module-coverage-client/check-client-module-coverage.mjs
104+
coverageReporters: ['clover', 'json', 'lcov', 'text-summary','json-summary'],
104105
setupFilesAfterEnv: ['<rootDir>/src/test/javascript/spec/jest-test-setup.ts', 'jest-extended/all'],
105106
moduleFileExtensions: ['ts', 'html', 'js', 'json', 'mjs'],
106107
transformIgnorePatterns: [`/node_modules/(?!${esModules})`],

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@
184184
"clean-www": "rimraf build/resources/main/static/app/{src,build/}",
185185
"compile:ts": "npm run prebuild && tsc --project tsconfig.app.json",
186186
"compile:ts:tests": "npm run prebuild && tsc --project tsconfig.spec.json",
187+
"check-module-coverage": "node supporting_scripts/code-coverage/module-coverage-client/check-client-module-coverage.mjs",
187188
"lint": "eslint",
188189
"lint:fix": "eslint --fix",
189190
"postinstall": "husky && patch-package && node src/main/webapp/app/lecture/manage/pdf-preview/pdfjs_copy_worker_script.mjs",
@@ -194,7 +195,7 @@
194195
"start": "npm run prebuild -- --develop && ng serve --hmr",
195196
"test-diff:ci": "git fetch origin develop && npm run prebuild && ng test --log-heap-usage -w=4 --ci --reporters=default --reporters=jest-junit --pass-with-no-tests --changed-since=origin/develop",
196197
"test-diff": "npm run prebuild && ng test --log-heap-usage -w=4 --pass-with-no-tests --changed-since=origin/develop --coverage",
197-
"test:ci": "npm run prebuild && ng test --coverage --log-heap-usage -w=4 --ci --reporters=default --reporters=jest-junit",
198+
"test:ci": "npm run prebuild && ng test --coverage --log-heap-usage -w=4 --ci --reporters=default --reporters=jest-junit && npm run check-module-coverage",
198199
"test:leaks": "npm run prebuild && ng test --log-heap-usage --detect-leaks",
199200
"test:open-handles": "npm run prebuild && ng test --detect-open-handles",
200201
"test": "npm run prebuild && ng test --coverage --log-heap-usage -w=4",

src/main/java/de/tum/cit/aet/artemis/athena/config/AthenaHealthIndicator.java

Lines changed: 9 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@
1313
import org.springframework.stereotype.Component;
1414
import org.springframework.web.client.RestTemplate;
1515

16-
import com.fasterxml.jackson.databind.JsonNode;
17-
1816
import de.tum.cit.aet.artemis.core.service.connectors.ConnectorHealth;
1917

2018
/**
@@ -30,18 +28,8 @@ public class AthenaHealthIndicator implements HealthIndicator {
3028

3129
private static final String ATHENA_URL_KEY = "url";
3230

33-
private static final String ATHENA_STATUS_KEY = "status";
34-
35-
private static final String ATHENA_MODULES_KEY = "modules";
36-
3731
private static final String ATHENA_ASSESSMENT_MODULE_MANAGER_KEY = "assessment module manager";
3832

39-
private static final String ATHENA_MODULE_URL_KEY = "url";
40-
41-
private static final String ATHENA_MODULE_EXERCISE_TYPE_KEY = "type";
42-
43-
private static final String ATHENA_MODULE_HEALTHY_KEY = "healthy";
44-
4533
private final RestTemplate shortTimeoutRestTemplate;
4634

4735
@Value("${artemis.athena.url}")
@@ -69,12 +57,12 @@ public Health health() {
6957
additionalInfo.put(ATHENA_URL_KEY, athenaUrl);
7058
ConnectorHealth health;
7159
try {
72-
final var response = shortTimeoutRestTemplate.getForObject(athenaUrl + "/health", JsonNode.class);
73-
final var athenaStatus = response != null ? response.get(ATHENA_STATUS_KEY).asText() : null;
60+
final var healthResponse = shortTimeoutRestTemplate.getForObject(athenaUrl + "/health", AthenaHealthResponse.class);
61+
final var athenaStatus = healthResponse != null ? healthResponse.status() : null;
7462

7563
if (athenaStatus != null) {
7664
additionalInfo.put(ATHENA_ASSESSMENT_MODULE_MANAGER_KEY, athenaStatus);
77-
additionalInfo.putAll(getAdditionalInfoForModules(response));
65+
additionalInfo.putAll(getAdditionalInfoForModules(healthResponse));
7866
}
7967
else {
8068
additionalInfo.put(ATHENA_ASSESSMENT_MODULE_MANAGER_KEY, "not available");
@@ -91,17 +79,13 @@ public Health health() {
9179
/**
9280
* Get additional information about the health of the assessment modules.
9381
*/
94-
private Map<String, Object> getAdditionalInfoForModules(JsonNode athenaHealthResponse) {
82+
private Map<String, Object> getAdditionalInfoForModules(AthenaHealthResponse athenaHealthResponse) {
9583
var additionalModuleInfo = new HashMap<String, Object>();
96-
if (athenaHealthResponse.has(ATHENA_MODULES_KEY)) {
97-
JsonNode modules = athenaHealthResponse.get(ATHENA_MODULES_KEY);
98-
// keys are module names, values are description maps
99-
modules.fields().forEachRemaining(module -> {
100-
var moduleHealth = new AthenaModuleHealth(module.getValue().get(ATHENA_MODULE_EXERCISE_TYPE_KEY).asText(),
101-
module.getValue().get(ATHENA_MODULE_HEALTHY_KEY).asBoolean(), module.getValue().get(ATHENA_MODULE_URL_KEY).asText());
102-
additionalModuleInfo.put(module.getKey(), moduleHealthToString(moduleHealth));
103-
});
104-
}
84+
var modules = athenaHealthResponse.modules();
85+
modules.forEach((moduleName, descriptionMap) -> {
86+
var moduleHealth = new AthenaModuleHealth(descriptionMap.type(), descriptionMap.healthy(), descriptionMap.url());
87+
additionalModuleInfo.put(moduleName, moduleHealthToString(moduleHealth));
88+
});
10589
return additionalModuleInfo;
10690
}
10791
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package de.tum.cit.aet.artemis.athena.config;
2+
3+
import java.util.Map;
4+
5+
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
6+
import com.fasterxml.jackson.annotation.JsonInclude;
7+
8+
@JsonIgnoreProperties(ignoreUnknown = true)
9+
@JsonInclude(JsonInclude.Include.NON_EMPTY)
10+
public record AthenaHealthResponse(String status, Map<String, ModuleHealth> modules) {
11+
12+
public record ModuleHealth(String url, String type, boolean healthy, boolean supportsEvaluation, boolean supportsNonGradedFeedbackRequests,
13+
boolean supportsGradedFeedbackRequests) {
14+
}
15+
}

src/main/java/de/tum/cit/aet/artemis/atlas/web/LearnerProfileResource.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
import de.tum.cit.aet.artemis.core.exception.BadRequestAlertException;
3030
import de.tum.cit.aet.artemis.core.repository.UserRepository;
3131
import de.tum.cit.aet.artemis.core.security.annotations.EnforceAtLeastStudent;
32-
import de.tum.cit.aet.artemis.core.service.CourseService;
32+
import de.tum.cit.aet.artemis.core.service.course.CourseAtlasService;
3333

3434
@Conditional(AtlasEnabled.class)
3535
@RestController
@@ -42,15 +42,15 @@ public class LearnerProfileResource {
4242

4343
private final CourseLearnerProfileRepository courseLearnerProfileRepository;
4444

45-
private final CourseService courseService;
45+
private final CourseAtlasService courseAtlasService;
4646

4747
private final CourseLearnerProfileService courseLearnerProfileService;
4848

49-
public LearnerProfileResource(UserRepository userRepository, CourseLearnerProfileRepository courseLearnerProfileRepository, CourseService courseService,
49+
public LearnerProfileResource(UserRepository userRepository, CourseLearnerProfileRepository courseLearnerProfileRepository, CourseAtlasService courseAtlasService,
5050
CourseLearnerProfileService courseLearnerProfileService) {
5151
this.userRepository = userRepository;
5252
this.courseLearnerProfileRepository = courseLearnerProfileRepository;
53-
this.courseService = courseService;
53+
this.courseAtlasService = courseAtlasService;
5454
this.courseLearnerProfileService = courseLearnerProfileService;
5555
}
5656

@@ -68,7 +68,7 @@ public ResponseEntity<Set<CourseLearnerProfileDTO>> getCourseLearnerProfiles() {
6868
Set<CourseLearnerProfile> courseLearnerProfiles = courseLearnerProfileRepository.findAllByLoginAndCourseActive(user.getLogin(), ZonedDateTime.now()).stream()
6969
.filter(profile -> user.getGroups().contains(profile.getCourse().getStudentGroupName())).collect(Collectors.toSet());
7070

71-
Set<Course> coursesWithLearningPaths = courseService.findAllActiveForUserAndLearningPathsEnabled(user);
71+
Set<Course> coursesWithLearningPaths = courseAtlasService.findAllActiveForUserAndLearningPathsEnabled(user);
7272

7373
// This is needed, as there is no method that is executed everytime a user is added to a new course
7474
Set<CourseLearnerProfile> newProfiles = coursesWithLearningPaths.stream()

src/main/java/de/tum/cit/aet/artemis/atlas/web/LearningPathResource.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
import de.tum.cit.aet.artemis.core.security.annotations.enforceRoleInCourse.EnforceAtLeastInstructorInCourse;
5454
import de.tum.cit.aet.artemis.core.security.annotations.enforceRoleInCourse.EnforceAtLeastStudentInCourse;
5555
import de.tum.cit.aet.artemis.core.service.AuthorizationCheckService;
56-
import de.tum.cit.aet.artemis.core.service.CourseService;
56+
import de.tum.cit.aet.artemis.core.service.course.CourseService;
5757
import de.tum.cit.aet.artemis.core.service.feature.Feature;
5858
import de.tum.cit.aet.artemis.core.service.feature.FeatureToggle;
5959

src/main/java/de/tum/cit/aet/artemis/communication/repository/ConversationMessageRepository.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ private Specification<Post> configureSearchSpecification(Specification<Post> spe
7070
* @return returns a Page of Messages
7171
*/
7272
default Page<Post> findMessages(PostContextFilterDTO postContextFilter, Pageable pageable, long userId) {
73-
var specification = Specification.where(getConversationsSpecification(postContextFilter.conversationIds()));
73+
var specification = getConversationsSpecification(postContextFilter.conversationIds());
7474
specification = configureSearchSpecification(specification, postContextFilter, userId);
7575
// Fetch all necessary attributes to avoid lazy loading (even though relations are defined as EAGER in the domain class, specification queries do not respect this)
7676
return findPostsWithSpecification(pageable, specification);

src/main/java/de/tum/cit/aet/artemis/communication/repository/MessageSpecs.java

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import jakarta.persistence.criteria.JoinType;
1010
import jakarta.persistence.criteria.Order;
1111
import jakarta.persistence.criteria.Predicate;
12+
import jakarta.validation.constraints.NotNull;
1213

1314
import org.apache.commons.lang3.StringUtils;
1415
import org.springframework.data.jpa.domain.Specification;
@@ -42,12 +43,15 @@ public static Specification<Post> getSearchTextAndAuthorSpecification(String sea
4243
return (root, query, criteriaBuilder) -> {
4344
boolean hasText = searchText != null && !searchText.isBlank();
4445
boolean hasAuthors = authorIds != null && authorIds.length > 0;
46+
// no author ids and no search text means no filtering
4547
if (!hasText && !hasAuthors) {
4648
return null;
4749
}
50+
// if only a search text is given, use the search text specification
4851
if (hasText && !hasAuthors) {
4952
return getSearchTextSpecification(searchText).toPredicate(root, query, criteriaBuilder);
5053
}
54+
// if only author ids are given, use the author specification
5155
if (!hasText && hasAuthors) {
5256
return getAuthorSpecification(authorIds).toPredicate(root, query, criteriaBuilder);
5357
}
@@ -74,6 +78,7 @@ public static Specification<Post> getSearchTextAndAuthorSpecification(String sea
7478
* @param searchText Text to be searched within messages
7579
* @return specification used to chain DB operations
7680
*/
81+
@NotNull
7782
public static Specification<Post> getSearchTextSpecification(String searchText) {
7883
return ((root, query, criteriaBuilder) -> {
7984
if (searchText == null || searchText.isBlank()) {
@@ -103,6 +108,7 @@ else if (searchText.startsWith("#") && StringUtils.isNumeric(searchText.substrin
103108
* @param conversationIds ids of the conversation messages belong to
104109
* @return specification used to chain DB operations
105110
*/
111+
@NotNull
106112
public static Specification<Post> getConversationsSpecification(long[] conversationIds) {
107113
return ((root, query, criteriaBuilder) -> {
108114
if (conversationIds == null || conversationIds.length == 0) {
@@ -121,6 +127,7 @@ public static Specification<Post> getConversationsSpecification(long[] conversat
121127
* @param courseId id of course the posts belong to
122128
* @return specification used to chain DB operations
123129
*/
130+
@NotNull
124131
public static Specification<Post> getCourseWideChannelsSpecification(boolean filterToCourseWide, Long courseId) {
125132
return (root, query, criteriaBuilder) -> {
126133
if (!filterToCourseWide) {
@@ -142,6 +149,7 @@ public static Specification<Post> getCourseWideChannelsSpecification(boolean fil
142149
* @param authorIds ids of the post authors
143150
* @return specification used to chain DB operations
144151
*/
152+
@NotNull
145153
public static Specification<Post> getAuthorSpecification(long[] authorIds) {
146154
return ((root, query, criteriaBuilder) -> {
147155
if (authorIds == null || authorIds.length == 0) {
@@ -164,6 +172,7 @@ public static Specification<Post> getAuthorSpecification(long[] authorIds) {
164172
* @param userId id of the calling user
165173
* @return specification used to chain DB operations
166174
*/
175+
@NotNull
167176
public static Specification<Post> getAnsweredOrReactedSpecification(boolean answeredOrReacted, Long userId) {
168177
return ((root, query, criteriaBuilder) -> {
169178
if (!answeredOrReacted) {
@@ -190,6 +199,7 @@ public static Specification<Post> getAnsweredOrReactedSpecification(boolean answ
190199
* @param unresolved whether only the Posts without resolving answers should be fetched or not
191200
* @return specification used to chain DB operations
192201
*/
202+
@NotNull
193203
public static Specification<Post> getUnresolvedSpecification(boolean unresolved) {
194204
return ((root, query, criteriaBuilder) -> {
195205
if (!unresolved) {
@@ -218,9 +228,10 @@ public static Specification<Post> getUnresolvedSpecification(boolean unresolved)
218228
* @param sortingOrder direction of sorting (ASC, DESC)
219229
* @return specification used to chain DB operations
220230
*/
231+
@NotNull
221232
public static Specification<Post> getSortSpecification(boolean pagingEnabled, PostSortCriterion postSortCriterion, SortingOrder sortingOrder) {
222233
return ((root, query, criteriaBuilder) -> {
223-
if (pagingEnabled && postSortCriterion != null && sortingOrder != null) {
234+
if (pagingEnabled && postSortCriterion != null && sortingOrder != null && query != null) {
224235

225236
List<Order> orderList = new ArrayList<>();
226237

@@ -246,9 +257,12 @@ public static Specification<Post> getSortSpecification(boolean pagingEnabled, Po
246257
* incompatible with each other at server tests
247258
* <a href="https://github.com/h2database/h2database/issues/408">...</a>
248259
*/
260+
@NotNull
249261
public static Specification<Post> distinct() {
250262
return (root, query, criteriaBuilder) -> {
251-
query.groupBy(root.get(Post_.ID));
263+
if (query != null) {
264+
query.groupBy(root.get(Post_.ID));
265+
}
252266
return null;
253267
};
254268
}
@@ -259,6 +273,7 @@ public static Specification<Post> distinct() {
259273
* @param pinnedOnly whether only pinned posts should be fetched
260274
* @return specification used to chain DB operations
261275
*/
276+
@NotNull
262277
public static Specification<Post> getPinnedSpecification(boolean pinnedOnly) {
263278
return (root, query, criteriaBuilder) -> {
264279
if (!pinnedOnly) {

src/main/java/de/tum/cit/aet/artemis/core/repository/UserRepository.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -786,9 +786,9 @@ default Page<UserDTO> getAllManagedUsers(UserPageableSearchDTO userSearch) {
786786
var noRegistrationNumber = userSearch.getRegistrationNumbers().contains(FILTER_WITHOUT_REG_NO);
787787
var withRegistrationNumber = userSearch.getRegistrationNumbers().contains(FILTER_WITH_REG_NO);
788788

789-
Specification<User> specification = Specification.where(distinct()).and(notSoftDeleted()).and(getSearchTermSpecification(searchTerm))
790-
.and(getInternalOrExternalSpecification(internal, external)).and(getActivatedOrDeactivatedSpecification(activated, deactivated))
791-
.and(getAuthoritySpecification(modifiedAuthorities)).and(getWithOrWithoutRegistrationNumberSpecification(noRegistrationNumber, withRegistrationNumber));
789+
Specification<User> specification = distinct().and(notSoftDeleted()).and(getSearchTermSpecification(searchTerm)).and(getInternalOrExternalSpecification(internal, external))
790+
.and(getActivatedOrDeactivatedSpecification(activated, deactivated)).and(getAuthoritySpecification(modifiedAuthorities))
791+
.and(getWithOrWithoutRegistrationNumberSpecification(noRegistrationNumber, withRegistrationNumber));
792792

793793
if (userSearch.isFindWithoutUserGroups()) {
794794
specification = specification.and(getAllUsersWithoutUserGroups());

0 commit comments

Comments
 (0)