Skip to content

Commit

Permalink
Merge branch 'develop' into bugfix/lectures/fix-lecture-unit-date-val…
Browse files Browse the repository at this point in the history
…idation
  • Loading branch information
florian-glombik authored Nov 20, 2024
2 parents 3d3f513 + 5024862 commit 0ebbb85
Show file tree
Hide file tree
Showing 52 changed files with 346 additions and 422 deletions.
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
2 changes: 1 addition & 1 deletion 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 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
@@ -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
Original file line number Diff line number Diff line change
@@ -1,28 +1,30 @@
import { Component, Input } from '@angular/core';
import { Component, Input, inject } from '@angular/core';
import { HttpErrorResponse, HttpResponse } from '@angular/common/http';
import { ActivatedRoute, Router } from '@angular/router';
import { TranslateService } from '@ngx-translate/core';
import { filter } from 'rxjs/operators';
import { FileUploadExercise } from 'app/entities/file-upload-exercise.model';
import { FileUploadExerciseService } from './file-upload-exercise.service';
import { ExerciseComponent } from 'app/exercises/shared/exercise/exercise.component';
import { onError } from 'app/shared/util/global.utils';
import { AccountService } from 'app/core/auth/account.service';
import { CourseManagementService } from 'app/course/manage/course-management.service';
import { SortService } from 'app/shared/service/sort.service';
import { ExerciseService } from 'app/exercises/shared/exercise/exercise.service';
import { AlertService } from 'app/core/util/alert.service';
import { EventManager } from 'app/core/util/event-manager.service';
import { faBook, faPlus, faSort, faTable, faTrash, faUsers, faWrench } from '@fortawesome/free-solid-svg-icons';
import { faListAlt } from '@fortawesome/free-regular-svg-icons';
import { CourseExerciseService } from 'app/exercises/shared/course-exercises/course-exercise.service';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';

@Component({
selector: 'jhi-file-upload-exercise',
templateUrl: './file-upload-exercise.component.html',
})
export class FileUploadExerciseComponent extends ExerciseComponent {
protected exerciseService = inject(ExerciseService);
protected fileUploadExerciseService = inject(FileUploadExerciseService);
private courseExerciseService = inject(CourseExerciseService);
private alertService = inject(AlertService);
private accountService = inject(AccountService);
private sortService = inject(SortService);

@Input() fileUploadExercises: FileUploadExercise[] = [];
filteredFileUploadExercises: FileUploadExercise[] = [];

Expand All @@ -40,23 +42,6 @@ export class FileUploadExerciseComponent extends ExerciseComponent {
return this.fileUploadExercises;
}

constructor(
public exerciseService: ExerciseService,
public fileUploadExerciseService: FileUploadExerciseService,
private courseExerciseService: CourseExerciseService,
private alertService: AlertService,
private accountService: AccountService,
private modalService: NgbModal,
private router: Router,
private sortService: SortService,
courseService: CourseManagementService,
translateService: TranslateService,
eventManager: EventManager,
route: ActivatedRoute,
) {
super(courseService, translateService, route, eventManager);
}

protected loadExercises(): void {
this.courseExerciseService
.findAllFileUploadExercisesForCourse(this.courseId)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
import { Component, Input } from '@angular/core';
import { Component, Input, inject } from '@angular/core';
import { HttpErrorResponse, HttpResponse } from '@angular/common/http';
import { ModelingExercise } from 'app/entities/modeling-exercise.model';
import { ModelingExerciseService } from './modeling-exercise.service';
import { AccountService } from 'app/core/auth/account.service';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { ActivatedRoute, Router } from '@angular/router';
import { CourseManagementService } from 'app/course/manage/course-management.service';
import { ExerciseComponent } from 'app/exercises/shared/exercise/exercise.component';
import { TranslateService } from '@ngx-translate/core';
import { onError } from 'app/shared/util/global.utils';
import { SortService } from 'app/shared/service/sort.service';
import { ExerciseService } from 'app/exercises/shared/exercise/exercise.service';
import { AlertService } from 'app/core/util/alert.service';
import { EventManager } from 'app/core/util/event-manager.service';
import { faBook, faPlus, faSort, faTable, faTimes, faTrash, faUsers, faWrench } from '@fortawesome/free-solid-svg-icons';
import { faListAlt } from '@fortawesome/free-regular-svg-icons';
import { CourseExerciseService } from 'app/exercises/shared/course-exercises/course-exercise.service';
Expand All @@ -22,7 +17,14 @@ import { CourseExerciseService } from 'app/exercises/shared/course-exercises/cou
templateUrl: './modeling-exercise.component.html',
})
export class ModelingExerciseComponent extends ExerciseComponent {
@Input() modelingExercises: ModelingExercise[];
protected exerciseService = inject(ExerciseService);
protected modelingExerciseService = inject(ModelingExerciseService);
private courseExerciseService = inject(CourseExerciseService);
private alertService = inject(AlertService);
private accountService = inject(AccountService);
private sortService = inject(SortService);

@Input() modelingExercises: ModelingExercise[] = [];
filteredModelingExercises: ModelingExercise[];
// Icons
faPlus = faPlus;
Expand All @@ -39,24 +41,6 @@ export class ModelingExerciseComponent extends ExerciseComponent {
return this.modelingExercises;
}

constructor(
public exerciseService: ExerciseService,
public modelingExerciseService: ModelingExerciseService,
private courseExerciseService: CourseExerciseService,
private alertService: AlertService,
private accountService: AccountService,
private sortService: SortService,
private modalService: NgbModal,
private router: Router,
courseService: CourseManagementService,
translateService: TranslateService,
eventManager: EventManager,
route: ActivatedRoute,
) {
super(courseService, translateService, route, eventManager);
this.modelingExercises = [];
}

protected loadExercises(): void {
this.courseExerciseService.findAllModelingExercisesForCourse(this.courseId).subscribe({
next: (res: HttpResponse<ModelingExercise[]>) => {
Expand Down
Loading

0 comments on commit 0ebbb85

Please sign in to comment.