Skip to content

Commit

Permalink
Merge branch 'develop' into feature/programming-exercises/versioned-b…
Browse files Browse the repository at this point in the history
…uild-images
  • Loading branch information
magaupp authored Nov 21, 2024
2 parents 6d2abda + 5efb5f4 commit 93f0526
Show file tree
Hide file tree
Showing 73 changed files with 513 additions and 558 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ Refer to [Using JHipster in production](http://www.jhipster.tech/production) for
The following command can automate the deployment to a server. The example shows the deployment to the main Artemis test server (which runs a virtual machine):

```shell
./artemis-server-cli deploy [email protected] -w build/libs/Artemis-7.7.1.war
./artemis-server-cli deploy [email protected] -w build/libs/Artemis-7.7.2.war
```

## Architecture
Expand Down
22 changes: 17 additions & 5 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ plugins {
}

group = "de.tum.cit.aet.artemis"
version = "7.7.1"
version = "7.7.2"
description = "Interactive Learning with Individual Feedback"

java {
Expand Down Expand Up @@ -345,7 +345,6 @@ dependencies {
// use newest version of commons-compress to avoid security issues through outdated dependencies
implementation "org.apache.commons:commons-compress:1.27.1"


// import JHipster dependencies BOM
implementation platform("tech.jhipster:jhipster-dependencies:${jhipster_dependencies_version}")

Expand Down Expand Up @@ -377,7 +376,7 @@ dependencies {
implementation "javax.cache:cache-api:1.1.1"
implementation "org.hibernate.orm:hibernate-core:${hibernate_version}"

implementation "com.zaxxer:HikariCP:6.1.0"
implementation "com.zaxxer:HikariCP:6.2.1"

implementation "org.apache.commons:commons-text:1.12.0"
implementation "org.apache.commons:commons-math3:3.6.1"
Expand Down Expand Up @@ -414,8 +413,13 @@ dependencies {
implementation "org.springframework.cloud:spring-cloud-starter-config:4.1.3"
implementation "org.springframework.cloud:spring-cloud-commons:4.1.4"

implementation "io.netty:netty-all:4.1.115.Final"
implementation "io.projectreactor.netty:reactor-netty:1.2.0"
implementation("io.netty:netty-common") {
version {
strictly netty_version
}
}

implementation "org.springframework:spring-messaging:6.1.14"
implementation "org.springframework.retry:spring-retry:2.0.10"

Expand Down Expand Up @@ -546,6 +550,14 @@ dependencies {
exclude group: "org.testcontainers", module: "mariadb"
exclude group: "org.testcontainers", module: "mssqlserver"
}
testImplementation "org.testcontainers:testcontainers:1.20.3"
testImplementation "org.testcontainers:mysql:1.20.3"
testImplementation "org.testcontainers:postgresql:1.20.3"
testImplementation "org.testcontainers:testcontainers:1.20.3"
testImplementation "org.testcontainers:junit-jupiter:1.20.3"
testImplementation "org.testcontainers:jdbc:1.20.3"
testImplementation "org.testcontainers:database-commons:1.20.3"

testImplementation "com.tngtech.archunit:archunit:1.3.0"
testImplementation("org.skyscreamer:jsonassert:1.5.3") {
exclude module: "android-json"
Expand Down Expand Up @@ -597,7 +609,7 @@ tasks.withType(Test).configureEach {

afterTest { descriptor, result ->
if (result.resultType == TestResult.ResultType.FAILURE) {
String failedTest = "${descriptor.className}::${descriptor.name}"
var failedTest = "${descriptor.className}::${descriptor.name}"
logger.debug("Adding " + failedTest + " to failedTests...")
failedTests << [failedTest]
}
Expand Down
11 changes: 5 additions & 6 deletions docs/dev/setup/docker-compose.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Getting Started with Docker Compose
Make sure that Docker Desktop has enough memory (~ 6GB). To adapt it, go to ``Settings -> Resources``.

2. Check that all local network ports used by Docker Compose are free (e.g. you haven't started a local MySQL server
when you would like to start a Docker Compose instance of mysql)
when you would like to start a Docker Compose instance of MySQL).
3. Run ``docker compose pull && docker compose up`` in the directory ``docker/``
4. Open the Artemis instance in your browser at https://localhost
5. Run ``docker compose down`` in the directory ``docker/`` to stop and remove the docker containers
Expand Down Expand Up @@ -62,13 +62,12 @@ Three example commands to run such setups:

.. code:: bash
docker compose -f docker/atlassian.yml up
docker compose -f docker/mysql.yml -f docker/gitlab-jenkins.yml up
docker compose -f docker/artemis-dev-postgres.yml up
.. tip::
There is also a single ``docker-compose.yml`` in the directory ``docker/`` which mirrors the setup of ``artemis-prod-mysql.yml``.
This should provide a quick way, without manual changes necessary, for new contributors to startup an Artemis instance.
This should provide a quick way, without manual changes necessary, for new contributors to start up an Artemis instance.
If the documentation just mentions to run ``docker compose`` without a ``-f <file.yml>`` argument, it's
assumed you are running the command from the ``docker/`` directory.

Expand All @@ -82,7 +81,7 @@ is defined in the following files:
* ``gitlab.yml``: **GitLab Service**
* ``jenkins.yml``: **Jenkins Service**

For testing mails or SAML logins, you can append the following services to any setup with an artemis container:
For testing mails or SAML logins, you can append the following services to any setup with an Artemis container:

* ``mailhog.yml``: **Mailhog Service** (email testing tool)
* ``saml-test.yml``: **Saml-Test Service** (SAML Test Identity Provider for testing SAML features)
Expand Down Expand Up @@ -145,7 +144,7 @@ Get a shell into the containers
``docker compose exec artemis-app bash`` or if the container is not yet running:
``docker compose run --rm artemis-app bash``
- mysql container:
``docker compose exec mysql bash`` or directly into mysql ``docker compose exec mysql mysql``
``docker compose exec mysql bash`` or directly into MySQL ``docker compose exec mysql mysql``

Analog for other services.

Expand All @@ -157,7 +156,7 @@ Other useful commands
- Stop, remove containers and volumes: ``docker compose down -v``
- Remove Artemis-related volumes/state: ``docker volume rm artemis-data artemis-mysql-data``

This is helpful in setups where you just want to delete the state of artemis
This is helpful in setups where you just want to delete the state of Artemis
but not of Jenkins and GitLab for instance.
- Stop a service: ``docker compose stop <name of the service>`` (restart via
``docker compose start <name of the service>``)
Expand Down
3 changes: 2 additions & 1 deletion docs/dev/setup/jenkins-gitlab.rst
Original file line number Diff line number Diff line change
Expand Up @@ -890,7 +890,8 @@ and the corresponding Docker image can be found on

.. code:: bash
docker compose -f docker/<Jenkins setup to be launched>.yml up --build -d
docker compose -f docker/<Jenkins setup to be launched>.yml build --no-cache
docker compose -f docker/<Jenkins setup to be launched>.yml up -d
3. Build the new Docker image:

Expand Down
5 changes: 3 additions & 2 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ npm_version=10.8.0
# Dependency versions
jhipster_dependencies_version=8.7.2
spring_boot_version=3.3.5
spring_security_version=6.3.4
spring_security_version=6.3.5
# TODO: upgrading to 6.6.0 currently leads to issues due to internal changes in Hibernate and potentially wrong use in Artemis server code
hibernate_version=6.4.10.Final
# TODO: can we update to 5.x?
Expand All @@ -31,10 +31,11 @@ docker_java_version=3.4.0
logback_version=1.5.12
java_parser_version=3.26.2
byte_buddy_version=1.15.10
netty_version=4.1.115.Final

# testing
# make sure both versions are compatible
junit_version=5.11.0
junit_version=5.11.3
junit_platform_version=1.11.3
mockito_version=5.14.2

Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "artemis",
"version": "7.7.1",
"version": "7.7.2",
"description": "Interactive Learning with Individual Feedback",
"private": true,
"license": "MIT",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package de.tum.cit.aet.artemis.communication.repository;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;

import jakarta.persistence.EntityManager;
Expand Down Expand Up @@ -50,6 +52,8 @@ public Page<Long> findPostIdsWithSpecification(Specification<Post> specification
query.setMaxResults(pageable.getPageSize());

List<Long> postIds = query.getResultList();
// removes all duplicates from the answer posts
List<Long> uniquePostIds = new ArrayList<>(new LinkedHashSet<>(postIds));

// Count query
CriteriaQuery<Long> countQuery = builder.createQuery(Long.class);
Expand All @@ -66,6 +70,6 @@ public Page<Long> findPostIdsWithSpecification(Specification<Post> specification
Long countResult = entityManager.createQuery(countQuery).getSingleResult();
long count = countResult != null ? countResult : 0L;

return new PageImpl<>(postIds, pageable, count);
return new PageImpl<>(uniquePostIds, pageable, count);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,15 @@ public class ConversationNotificationService {

private final SingleUserNotificationRepository singleUserNotificationRepository;

private final SingleUserNotificationService singleUserNotificationService;

public ConversationNotificationService(ConversationNotificationRepository conversationNotificationRepository,
GeneralInstantNotificationService generalInstantNotificationService, SingleUserNotificationRepository singleUserNotificationRepository) {
GeneralInstantNotificationService generalInstantNotificationService, SingleUserNotificationRepository singleUserNotificationRepository,
SingleUserNotificationService singleUserNotificationService) {
this.conversationNotificationRepository = conversationNotificationRepository;
this.generalInstantNotificationService = generalInstantNotificationService;
this.singleUserNotificationRepository = singleUserNotificationRepository;
this.singleUserNotificationService = singleUserNotificationService;
}

/**
Expand Down Expand Up @@ -83,7 +87,7 @@ public ConversationNotification createNotification(Post createdMessage, Conversa
String[] placeholders = createPlaceholdersNewMessageChannelText(course.getTitle(), createdMessage.getContent(), createdMessage.getCreationDate().toString(),
conversationName, createdMessage.getAuthor().getName(), conversationType);
ConversationNotification notification = createConversationMessageNotification(course.getId(), createdMessage, notificationType, notificationText, true, placeholders);
save(notification, mentionedUsers, placeholders);
save(notification, mentionedUsers, placeholders, createdMessage);
return notification;
}

Expand All @@ -93,11 +97,12 @@ public static String[] createPlaceholdersNewMessageChannelText(String courseTitl
return new String[] { courseTitle, messageContent, messageCreationDate, conversationName, authorName, conversationType };
}

private void save(ConversationNotification notification, Set<User> mentionedUsers, String[] placeHolders) {
private void save(ConversationNotification notification, Set<User> mentionedUsers, String[] placeHolders, Post createdMessage) {
conversationNotificationRepository.save(notification);

Set<SingleUserNotification> mentionedUserNotifications = mentionedUsers.stream().map(mentionedUser -> SingleUserNotificationFactory
.createNotification(notification.getMessage(), NotificationType.CONVERSATION_USER_MENTIONED, notification.getText(), placeHolders, mentionedUser))
Set<SingleUserNotification> mentionedUserNotifications = singleUserNotificationService
.filterAllowedRecipientsInMentionedUsers(mentionedUsers, createdMessage.getConversation()).map(mentionedUser -> SingleUserNotificationFactory
.createNotification(notification.getMessage(), NotificationType.CONVERSATION_USER_MENTIONED, notification.getText(), placeHolders, mentionedUser))
.collect(Collectors.toSet());
singleUserNotificationRepository.saveAll(mentionedUserNotifications);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -443,23 +443,34 @@ public void notifyInvolvedUsersAboutNewMessageReply(Post post, SingleUserNotific
usersInvolved.add(post.getAuthor());
}

mentionedUsers.stream().filter(user -> {
boolean isChannelAndCourseWide = post.getConversation() instanceof Channel channel && channel.getIsCourseWide();
boolean isChannelVisibleToStudents = !(post.getConversation() instanceof Channel channel) || conversationService.isChannelVisibleToStudents(channel);
boolean isChannelVisibleToMentionedUser = isChannelVisibleToStudents
|| authorizationCheckService.isAtLeastTeachingAssistantInCourse(post.getConversation().getCourse(), user);

// Only send a notification to the mentioned user if...
// (for course-wide channels) ...the course-wide channel is visible
// (for all other cases) ...the user is a member of the conversation
return (isChannelAndCourseWide && isChannelVisibleToMentionedUser) || conversationService.isMember(post.getConversation().getId(), user.getId());
}).forEach(mentionedUser -> notifyUserAboutNewMessageReply(savedAnswerMessage, notification, mentionedUser, author, CONVERSATION_USER_MENTIONED));
filterAllowedRecipientsInMentionedUsers(mentionedUsers, post.getConversation())
.forEach(mentionedUser -> notifyUserAboutNewMessageReply(savedAnswerMessage, notification, mentionedUser, author, CONVERSATION_USER_MENTIONED));

Conversation conv = conversationService.getConversationById(post.getConversation().getId());
usersInvolved.stream().filter(userInvolved -> !mentionedUsers.contains(userInvolved))
.forEach(userInvolved -> notifyUserAboutNewMessageReply(savedAnswerMessage, notification, userInvolved, author, getAnswerMessageNotificationType(conv)));
}

/**
* Filters which of the mentioned users are permitted to receive a notification
*
* @param mentionedUsers users mentioned in the answer message
* @param conversation the conversation of the created post/notification, used for filtering
* @return the stream of mentioned users which are permitted to receive the notification for the given conversation
*/
public Stream<User> filterAllowedRecipientsInMentionedUsers(Set<User> mentionedUsers, Conversation conversation) {
return mentionedUsers.stream().filter(user -> {
boolean isChannelAndCourseWide = conversation instanceof Channel channel && channel.getIsCourseWide();
boolean isChannelVisibleToStudents = !(conversation instanceof Channel channel) || conversationService.isChannelVisibleToStudents(channel);
boolean isChannelVisibleToMentionedUser = isChannelVisibleToStudents || authorizationCheckService.isAtLeastTeachingAssistantInCourse(conversation.getCourse(), user);

// Only send a notification to the mentioned user if...
// (for course-wide channels) ...the course-wide channel is visible
// (for all other cases) ...the user is a member of the conversation
return (isChannelAndCourseWide && isChannelVisibleToMentionedUser) || conversationService.isMember(conversation.getId(), user.getId());
});
}

/**
* Saves the given notification in database and sends it to the client via websocket.
* Also creates and sends an instant notification.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.springframework.context.annotation.Profile;
import org.springframework.core.env.Environment;
import org.springframework.http.MediaType;
import org.springframework.util.CollectionUtils;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
Expand Down Expand Up @@ -117,7 +118,7 @@ private String resolvePathPrefix() {
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = jHipsterProperties.getCors();
if (config.getAllowedOrigins() != null && !config.getAllowedOrigins().isEmpty()) {
if (!CollectionUtils.isEmpty(config.getAllowedOrigins()) || !CollectionUtils.isEmpty(config.getAllowedOriginPatterns())) {
log.debug("Registering CORS filter");
source.registerCorsConfiguration("/api/**", config);
source.registerCorsConfiguration("/management/**", config);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -338,11 +338,6 @@ Optional<ProgrammingExercise> findByIdWithEagerTestCasesStaticCodeAnalysisCatego
Optional<ProgrammingExercise> findByIdWithEagerBuildConfigTestCasesStaticCodeAnalysisCategoriesHintsAndTemplateAndSolutionParticipationsAndAuxReposAndSolutionEntriesAndBuildConfig(
@Param("exerciseId") long exerciseId);

default ProgrammingExercise findByIdWithEagerTestCasesStaticCodeAnalysisCategoriesHintsAndTemplateAndSolutionParticipationsAndAuxReposAndBuildConfigElseThrow(long exerciseId)
throws EntityNotFoundException {
return getValueElseThrow(findByIdWithEagerTestCasesStaticCodeAnalysisCategoriesHintsAndTemplateAndSolutionParticipationsAndAuxReposAndBuildConfig(exerciseId), exerciseId);
}

@Query("""
SELECT p
FROM ProgrammingExercise p
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<h4 class="exercise-title">
{{ exercise.title }}
<span
>[{{ exercise.maxPoints }} {{ 'artemisApp.examParticipation.points' | artemisTranslate }}]
>[{{ exercise.maxPoints }} {{ 'artemisApp.examParticipation.exercisePoints' | artemisTranslate }}]
@if (exercise.includedInOverallScore !== IncludedInOverallScore.INCLUDED_COMPLETELY) {
<jhi-included-in-score-badge [includedInOverallScore]="exercise.includedInOverallScore" />
}
Expand Down
Loading

0 comments on commit 93f0526

Please sign in to comment.