Skip to content

Commit

Permalink
Communication: Combine messages and communication view (#8801)
Browse files Browse the repository at this point in the history
  • Loading branch information
egekurt123 authored Jun 30, 2024
1 parent 6ae0625 commit 257eefc
Show file tree
Hide file tree
Showing 86 changed files with 212 additions and 2,063 deletions.
16 changes: 8 additions & 8 deletions docs/user/tutorialgroups.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Overview
Artemis facilitates the coordination of tutorial groups in a course. Tutorial groups are a learning strategy where students teach and learn from each other in small groups (20-30). In this strategy, proficient students act as tutors and lead the groups. The tutor and the group members usually meet weekly either on campus or online. Students present their solutions to homework assignments or other tasks and receive feedback and suggestions from the tutor and their peers.

.. note:: The tutorial group overview page (used by students) displays dates in the student's current time zone. The tutorial group management page (used by instructors and tutors to manage groups and their sessions) displays dates in the course time zone. This is helpful if the instructors or tutors travel and work across multiple time zones. A header at the top of the page shows the current time zone used for dates.

Setting up Tutorial Group Plan as an Instructor
-----------------------------------------------

Expand All @@ -35,7 +35,7 @@ Before the tutorial group feature can be used, three configurations need to be s

* **Default tutorial group period:** This is the semester period when the groups usually meet. It is used to prefill the meeting period when creating a new tutorial group. The tutorial group period can be changed later on for each group individually.

* **Artemis managed tutorial group channels:** This option allows Artemis to create and manage a dedicated channel for each tutorial group in the 'Messages' section of the course. This feature is only selectable if the course has the ``Messaging`` feature enabled in the course settings. If activated, tutorial group channels can still be managed manually but Artemis automatically performs some common tasks, such as:
* **Artemis managed tutorial group channels:** This option allows Artemis to create and manage a dedicated channel for each tutorial group in the 'Communication' section of the course. This feature is only selectable if the course has the ``Communication`` feature enabled in the course settings. If activated, tutorial group channels can still be managed manually but Artemis automatically performs some common tasks, such as:

* Adding and removing students from the channel when they register or unregister for the tutorial group

Expand Down Expand Up @@ -82,7 +82,7 @@ Managing Tutorial Groups as a Tutor

Assigned tutors can manage their tutorial groups by navigating to the course's ``Tutorial Groups`` page in the course administration section. The tutor can view the group's details, register or unregister students, and edit the sessions by cancelling or rescheduling them. The tutor also has moderation rights in the tutorial group's channel in the ``Messages`` section of the course if the ``Artemis managed tutorial group channels`` feature is enabled in the tutorial group settings.

The groups for which the tutor is responsible have a blue background.
The groups for which the tutor is responsible have a blue background.

.. note::
The instructor can perform the same actions as the tutor for all tutorial groups in the course. The tutor can only manage the tutorial groups that they are assigned to.
Expand Down Expand Up @@ -144,18 +144,18 @@ To see more details about a tutorial group (including an overview of all session
.. |instructors-csv-import| image:: tutorialgroups/instructors-csv-import.png
:width: 1000
.. |instructors-holidays| image:: tutorialgroups/instructors-holidays.png
:width: 1000
:width: 1000

.. |tutor-overview| image:: tutorialgroups/tutor-overview.png
:width: 1000
:width: 1000
.. |tutor-registrations| image:: tutorialgroups/tutor-registrations.png
:width: 1000
:width: 1000
.. |tutor-sessions| image:: tutorialgroups/tutor-sessions.png
:width: 1000
:width: 1000

.. |student-own-groups| image:: tutorialgroups/students-own-groups.png
:width: 1000
.. |student-all-groups| image:: tutorialgroups/students-all-groups.png
:width: 1000
.. |student-detail| image:: tutorialgroups/students-detail.png
:width: 1000
:width: 1000
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,9 @@ public enum CourseInformationSharingConfiguration {
/**
* Only Communication is enabled VALUE = 2
*/
COMMUNICATION_ONLY,
/**
* Only Messaging is enabled VALUE = 3
*/
MESSAGING_ONLY;
COMMUNICATION_ONLY;

public boolean isMessagingEnabled() {
return this == MESSAGING_ONLY || this == COMMUNICATION_AND_MESSAGING;
return this == COMMUNICATION_AND_MESSAGING;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -498,8 +498,7 @@ default Course findWithEagerLearningPathsAndCompetenciesByIdElseThrow(long cours
* @return true if the messaging feature is enabled for the course, false otherwise
*/
default boolean isMessagingEnabled(long courseId) {
return informationSharingConfigurationIsOneOf(courseId,
Set.of(CourseInformationSharingConfiguration.MESSAGING_ONLY, CourseInformationSharingConfiguration.COMMUNICATION_AND_MESSAGING));
return informationSharingConfigurationIsOneOf(courseId, Set.of(CourseInformationSharingConfiguration.COMMUNICATION_AND_MESSAGING));
}

/**
Expand All @@ -509,8 +508,8 @@ default boolean isMessagingEnabled(long courseId) {
* @return true if the communication feature is enabled for the course, false otherwise
*/
default boolean isMessagingOrCommunicationEnabled(long courseId) {
return informationSharingConfigurationIsOneOf(courseId, Set.of(CourseInformationSharingConfiguration.COMMUNICATION_ONLY,
CourseInformationSharingConfiguration.MESSAGING_ONLY, CourseInformationSharingConfiguration.COMMUNICATION_AND_MESSAGING));
return informationSharingConfigurationIsOneOf(courseId,
Set.of(CourseInformationSharingConfiguration.COMMUNICATION_ONLY, CourseInformationSharingConfiguration.COMMUNICATION_AND_MESSAGING));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,13 +184,10 @@ public List<ConversationDTO> getConversationsOfUser(Course course, User requesti

var groupChatsOfUser = groupChatRepository.findGroupChatsOfUserWithParticipantsAndUserGroups(course.getId(), requestingUser.getId());
conversationsOfUser.addAll(groupChatsOfUser);

channelsOfUser = channelRepository.findChannelsOfUser(course.getId(), requestingUser.getId());
}
else {
channelsOfUser = channelRepository.findCourseWideChannelsInCourse(course.getId());
}

channelsOfUser = channelRepository.findChannelsOfUser(course.getId(), requestingUser.getId());

// if the user is only a student in the course, we filter out all channels that are not yet open
var isOnlyStudent = authorizationCheckService.isOnlyStudentInCourse(course, requestingUser);
var filteredChannels = isOnlyStudent ? filterVisibleChannelsForStudents(channelsOfUser.stream()).toList() : channelsOfUser;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ public ChannelResource(ConversationParticipantRepository conversationParticipant
@EnforceAtLeastStudent
public ResponseEntity<List<ChannelDTO>> getCourseChannelsOverview(@PathVariable Long courseId) {
log.debug("REST request to all channels of course: {}", courseId);
checkMessagingEnabledElseThrow(courseId);
checkMessagingOrCommunicationEnabledElseThrow(courseId);
var requestingUser = userRepository.getUserWithGroupsAndAuthorities();
var course = courseRepository.findByIdElseThrow(courseId);
authorizationCheckService.checkHasAtLeastRoleInCourseElseThrow(Role.STUDENT, course, requestingUser);
Expand Down Expand Up @@ -215,7 +215,7 @@ public ResponseEntity<ChannelDTO> createChannel(@PathVariable Long courseId, @Re
log.debug("REST request to create channel in course {} with properties : {}", courseId, channelDTO);
var requestingUser = userRepository.getUserWithGroupsAndAuthorities();
var course = courseRepository.findByIdElseThrow(courseId);
checkMessagingEnabledElseThrow(course);
checkMessagingOrCommunicationEnabledElseThrow(course);
channelAuthorizationService.isAllowedToCreateChannel(course, requestingUser);

var channelToCreate = new Channel();
Expand Down Expand Up @@ -397,7 +397,7 @@ public ResponseEntity<Void> revokeChannelModeratorRole(@PathVariable Long course
public ResponseEntity<Void> registerUsersToChannel(@PathVariable Long courseId, @PathVariable Long channelId, @RequestBody(required = false) List<String> userLogins,
@RequestParam(defaultValue = "false") Boolean addAllStudents, @RequestParam(defaultValue = "false") Boolean addAllTutors,
@RequestParam(defaultValue = "false") Boolean addAllInstructors) {
checkMessagingEnabledElseThrow(courseId);
checkMessagingOrCommunicationEnabledElseThrow(courseId);
List<String> usersLoginsToRegister = new ArrayList<>();
if (userLogins != null) {
usersLoginsToRegister.addAll(userLogins);
Expand Down Expand Up @@ -433,7 +433,7 @@ public ResponseEntity<Void> registerUsersToChannel(@PathVariable Long courseId,
@PostMapping("{courseId}/channels/{channelId}/deregister")
@EnforceAtLeastStudent
public ResponseEntity<Void> deregisterUsers(@PathVariable Long courseId, @PathVariable Long channelId, @RequestBody List<String> userLogins) {
checkMessagingEnabledElseThrow(courseId);
checkMessagingOrCommunicationEnabledElseThrow(courseId);
if (userLogins == null || userLogins.isEmpty()) {
throw new BadRequestAlertException("No user logins provided", CHANNEL_ENTITY_NAME, "userLoginsEmpty");
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package de.tum.in.www1.artemis.web.rest.metis.conversation;

import java.util.Set;

import org.springframework.http.HttpStatus;
import org.springframework.web.server.ResponseStatusException;

Expand Down Expand Up @@ -36,8 +34,7 @@ void checkMessagingEnabledElseThrow(Long courseId) {
* @param course the course to check
*/
void checkMessagingEnabledElseThrow(Course course) {
if (!Set.of(CourseInformationSharingConfiguration.MESSAGING_ONLY, CourseInformationSharingConfiguration.COMMUNICATION_AND_MESSAGING)
.contains(course.getCourseInformationSharingConfiguration())) {
if (course.getCourseInformationSharingConfiguration() != CourseInformationSharingConfiguration.COMMUNICATION_AND_MESSAGING) {
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Messaging is not enabled for this course");
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
<changeSet id="20240620150000" author="ekurt">
<update tableName="course">
<column name="info_sharing_config" value="1"/>
<where>info_sharing_config = 3</where>
</update>
</changeSet>
</databaseChangeLog>
1 change: 1 addition & 0 deletions src/main/resources/config/liquibase/master.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
<include file="classpath:config/liquibase/changelog/20240617001400_changelog.xml" relativeToChangelogFile="false"/>
<include file="classpath:config/liquibase/changelog/20240612123000_changelog.xml" relativeToChangelogFile="false"/>
<include file="classpath:config/liquibase/changelog/20240614140000_changelog.xml" relativeToChangelogFile="false"/>
<include file="classpath:config/liquibase/changelog/20240620150000_changelog.xml" relativeToChangelogFile="false"/>
<!-- NOTE: please use the format "YYYYMMDDhhmmss_changelog.xml", i.e. year month day hour minutes seconds and not something else! -->
<!-- we should also stay in a chronological order! -->
<!-- you can use the command 'date '+%Y%m%d%H%M%S'' to get the current date and time in the correct format -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ export class LearningPathLectureUnitViewComponent {
this.discussionComponent = instance; // save the reference to the component instance
if (this.lecture) {
instance.lecture = this.lecture;
instance.isCommunicationPage = false;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,16 @@
<fa-icon [icon]="faChartBar" />
<span class="tab-link-text" jhiTranslate="artemisApp.courseOverview.menu.statistics"></span>
</a>
@if (isCommunicationEnabled) {
<a class="tab-link" [routerLink]="['/courses', course.id, 'discussion']" routerLinkActive="active">
<fa-icon [icon]="faComment" />
<span class="tab-link-text" jhiTranslate="artemisApp.metis.communication.label"></span>
<fa-icon [icon]="faArrowUpRightFromSquare" />
</a>
}
@if (course.isAtLeastInstructor && irisEnabled) {
<a class="tab-link" [routerLink]="['/course-management', course.id, 'iris-settings']" id="iris-settings" routerLinkActive="active">
<fa-icon [icon]="faRobot" />
<span class="tab-link-text" jhiTranslate="artemisApp.iris.settings.button.course.title"></span>
</a>
}
@if (isMessagingOrCommunicationEnabled) {
<a class="tab-link" [routerLink]="['/courses', course.id, 'messages']" routerLinkActive="active">
<a class="tab-link" [routerLink]="['/courses', course.id, 'communication']" routerLinkActive="active">
<fa-icon [icon]="faComments" />
<span class="tab-link-text" jhiTranslate="artemisApp.courseOverview.menu.messages"></span>
<span class="tab-link-text" jhiTranslate="artemisApp.metis.communication.label"></span>
<fa-icon [icon]="faArrowUpRightFromSquare" />
</a>
}
Expand Down
12 changes: 10 additions & 2 deletions src/main/webapp/app/course/manage/course-update.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,7 @@ <h5>
type="checkbox"
[(ngModel)]="communicationEnabled"
[ngModelOptions]="{ standalone: true }"
(change)="disableMessaging()"
/>
<label for="field_communicationEnabled" class="form-check-label">
{{ 'artemisApp.course.courseCommunicationSetting.communicationEnabled.label' | artemisTranslate }}
Expand All @@ -329,7 +330,14 @@ <h5>
</div>
<!-- Not part of model -->
<div class="form-check">
<input id="field_messagingEnabled" class="form-check-input" type="checkbox" [(ngModel)]="messagingEnabled" [ngModelOptions]="{ standalone: true }" />
<input
id="field_messagingEnabled"
class="form-check-input"
type="checkbox"
[(ngModel)]="messagingEnabled"
[ngModelOptions]="{ standalone: true }"
[disabled]="!communicationEnabled"
/>
<label for="field_messagingEnabled" class="form-check-label">
{{ 'artemisApp.course.courseCommunicationSetting.messagingEnabled.label' | artemisTranslate }}
</label>
Expand All @@ -339,7 +347,7 @@ <h5>
ngbTooltip="{{ 'artemisApp.course.courseCommunicationSetting.messagingEnabled.tooltip' | artemisTranslate }}"
/>
</div>
@if (messagingEnabled || communicationEnabled) {
@if (communicationEnabled) {
<div class="form-group">
<label class="form-control-label" jhiTranslate="artemisApp.codeOfConduct.title"></label>
<jhi-help-icon text="artemisApp.codeOfConduct.tooltip" />
Expand Down
7 changes: 5 additions & 2 deletions src/main/webapp/app/course/manage/course-update.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -297,9 +297,8 @@ export class CourseUpdateComponent implements OnInit {
course['courseInformationSharingConfiguration'] = CourseInformationSharingConfiguration.COMMUNICATION_AND_MESSAGING;
} else if (this.communicationEnabled && !this.messagingEnabled) {
course['courseInformationSharingConfiguration'] = CourseInformationSharingConfiguration.COMMUNICATION_ONLY;
} else if (!this.communicationEnabled && this.messagingEnabled) {
course['courseInformationSharingConfiguration'] = CourseInformationSharingConfiguration.MESSAGING_ONLY;
} else {
this.communicationEnabled = false;
course['courseInformationSharingConfiguration'] = CourseInformationSharingConfiguration.DISABLED;
}

Expand Down Expand Up @@ -648,6 +647,10 @@ export class CourseUpdateComponent implements OnInit {
}
});
}

disableMessaging() {
this.messagingEnabled = false;
}
}

const CourseValidator: ValidatorFn = (formGroup: FormGroup) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -277,21 +277,14 @@ <h4 class="text-center no-exercises mt-3 fw-medium">{{ 'artemisApp.course.noExer
<span class="d-none d-xl-inline">{{ 'artemisApp.courseStatistics.statistics' | artemisTranslate }}</span>
</a>
}
@if (course.isAtLeastTutor && isCommunicationEnabled(course)) {
@if (isCommunicationEnabled(course) && course.isAtLeastTutor) {
<a
[routerLink]="['/courses', course.id, 'discussion']"
[routerLink]="['/courses', course.id, 'communication']"
class="btn btn-primary me-1 mb-1"
[ngbTooltip]="'artemisApp.metis.communication.label' | artemisTranslate"
id="course-card-open-discussions"
[ngbTooltip]="'artemisApp.courseOverview.menu.communication' | artemisTranslate"
>
<fa-icon [icon]="faComment" />
<span class="d-none d-xl-inline">{{ 'artemisApp.metis.communication.label' | artemisTranslate }}</span>
</a>
}
@if (isMessagingEnabled(course) && course.isAtLeastTutor) {
<a [routerLink]="['/courses', course.id, 'messages']" class="btn btn-primary me-1 mb-1" [ngbTooltip]="'artemisApp.courseOverview.menu.messages' | artemisTranslate">
<fa-icon [icon]="faComments" />
<span class="d-none d-xl-inline">{{ 'artemisApp.courseOverview.menu.messages' | artemisTranslate }}</span>
<span class="d-none d-xl-inline">{{ 'artemisApp.metis.communication.label' | artemisTranslate }}</span>
</a>
}
@if (course.timeZone || course.isAtLeastInstructor) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ <h3>{{ 'artemisApp.plagiarism.plagiarismCases.conversation' | artemisTranslate }
[readOnlyMode]="false"
[posting]="posts[0]"
[showAnswers]="true"
[isCourseMessagesPage]="false"
[isCommunicationPage]="false"
[hasChannelModerationRights]="false"
/>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ export class TutorialGroupDetailComponent implements OnChanges {
title: 'artemisApp.entities.tutorialGroup.channel',
data: {
text: tutorialGroup.channel.name,
routerLink: tutorialGroup.channel.isMember ? ['/courses', this.course.id!, 'messages'] : undefined,
routerLink: tutorialGroup.channel.isMember ? ['/courses', this.course.id!, 'communication'] : undefined,
queryParams: { conversationId: tutorialGroup.channel.id },
},
});
Expand Down
Loading

0 comments on commit 257eefc

Please sign in to comment.