From 4e30d18e7ba800e611251d16c00059978fcf43a5 Mon Sep 17 00:00:00 2001 From: Asli Aykan Date: Mon, 16 Dec 2024 22:09:04 +0300 Subject: [PATCH 1/5] added new section to sidebar --- .../course-conversations.component.html | 1 - .../course-conversations.component.ts | 26 +++---- .../app/overview/course-overview.service.ts | 68 +++++++++++++++---- .../app/shared/sidebar/sidebar.component.ts | 3 +- src/main/webapp/app/types/sidebar.ts | 4 +- .../webapp/i18n/de/student-dashboard.json | 1 + .../webapp/i18n/en/student-dashboard.json | 1 + .../course/course-overview.service.spec.ts | 32 +++++++-- .../course-conversations.component.spec.ts | 46 +++++++++++++ 9 files changed, 140 insertions(+), 42 deletions(-) diff --git a/src/main/webapp/app/overview/course-conversations/course-conversations.component.html b/src/main/webapp/app/overview/course-conversations/course-conversations.component.html index 6b2bac7d8ada..7200a6e7b5ac 100644 --- a/src/main/webapp/app/overview/course-conversations/course-conversations.component.html +++ b/src/main/webapp/app/overview/course-conversations/course-conversations.component.html @@ -33,7 +33,6 @@ (onBrowsePressed)="openChannelOverviewDialog()" (onDirectChatPressed)="openCreateOneToOneChatDialog()" (onGroupChatPressed)="openCreateGroupChatDialog()" - [showAddOption]="CHANNEL_TYPE_SHOW_ADD_OPTION" [channelTypeIcon]="CHANNEL_TYPE_ICON" [sidebarItemAlwaysShow]="DEFAULT_SHOW_ALWAYS" [collapseState]="DEFAULT_COLLAPSE_STATE" diff --git a/src/main/webapp/app/overview/course-conversations/course-conversations.component.ts b/src/main/webapp/app/overview/course-conversations/course-conversations.component.ts index bdc17d480a70..57ea79fc6372 100644 --- a/src/main/webapp/app/overview/course-conversations/course-conversations.component.ts +++ b/src/main/webapp/app/overview/course-conversations/course-conversations.component.ts @@ -13,6 +13,7 @@ import { PageType, SortDirection } from 'app/shared/metis/metis.util'; import { faBan, faBookmark, + faClock, faComment, faComments, faFile, @@ -27,7 +28,7 @@ import { } from '@fortawesome/free-solid-svg-icons'; import { ButtonType } from 'app/shared/components/button.component'; import { CourseWideSearchComponent, CourseWideSearchConfig } from 'app/overview/course-conversations/course-wide-search/course-wide-search.component'; -import { AccordionGroups, ChannelAccordionShowAdd, ChannelTypeIcons, CollapseState, SidebarCardElement, SidebarData, SidebarItemShowAlways } from 'app/types/sidebar'; +import { AccordionGroups, ChannelTypeIcons, CollapseState, SidebarCardElement, SidebarData, SidebarItemShowAlways } from 'app/types/sidebar'; import { CourseOverviewService } from 'app/overview/course-overview.service'; import { GroupChatCreateDialogComponent } from 'app/overview/course-conversations/dialogs/group-chat-create-dialog/group-chat-create-dialog.component'; import { defaultFirstLayerDialogOptions, defaultSecondLayerDialogOptions } from 'app/overview/course-conversations/other/conversation.util'; @@ -44,6 +45,7 @@ import { canCreateChannel } from 'app/shared/metis/conversations/conversation-pe const DEFAULT_CHANNEL_GROUPS: AccordionGroups = { favoriteChannels: { entityData: [] }, + recently: { entityData: [] }, generalChannels: { entityData: [] }, exerciseChannels: { entityData: [] }, lectureChannels: { entityData: [] }, @@ -52,18 +54,6 @@ const DEFAULT_CHANNEL_GROUPS: AccordionGroups = { savedPosts: { entityData: [] }, }; -const CHANNEL_TYPE_SHOW_ADD_OPTION: ChannelAccordionShowAdd = { - generalChannels: true, - exerciseChannels: true, - examChannels: true, - groupChats: true, - directMessages: true, - favoriteChannels: false, - lectureChannels: true, - hiddenChannels: false, - savedPosts: false, -}; - const CHANNEL_TYPE_ICON: ChannelTypeIcons = { generalChannels: faMessage, exerciseChannels: faList, @@ -74,6 +64,7 @@ const CHANNEL_TYPE_ICON: ChannelTypeIcons = { lectureChannels: faFile, hiddenChannels: faBan, savedPosts: faBookmark, + recently: faClock, }; const DEFAULT_COLLAPSE_STATE: CollapseState = { @@ -86,6 +77,7 @@ const DEFAULT_COLLAPSE_STATE: CollapseState = { lectureChannels: true, hiddenChannels: true, savedPosts: true, + recently: true, }; const DEFAULT_SHOW_ALWAYS: SidebarItemShowAlways = { @@ -98,6 +90,7 @@ const DEFAULT_SHOW_ALWAYS: SidebarItemShowAlways = { lectureChannels: false, hiddenChannels: false, savedPosts: true, + recently: true, }; @Component({ @@ -135,7 +128,6 @@ export class CourseConversationsComponent implements OnInit, OnDestroy { openThreadOnFocus = false; selectedSavedPostStatus: null | SavedPostStatus = null; - readonly CHANNEL_TYPE_SHOW_ADD_OPTION = CHANNEL_TYPE_SHOW_ADD_OPTION; readonly CHANNEL_TYPE_ICON = CHANNEL_TYPE_ICON; readonly DEFAULT_COLLAPSE_STATE = DEFAULT_COLLAPSE_STATE; protected readonly DEFAULT_SHOW_ALWAYS = DEFAULT_SHOW_ALWAYS; @@ -409,8 +401,10 @@ export class CourseConversationsComponent implements OnInit, OnDestroy { prepareSidebarData() { this.metisConversationService.forceRefresh().subscribe({ complete: () => { - this.sidebarConversations = this.courseOverviewService.mapConversationsToSidebarCardElements(this.conversationsOfUser); - this.accordionConversationGroups = this.courseOverviewService.groupConversationsByChannelType(this.conversationsOfUser, this.messagingEnabled); + this.sidebarConversations = this.courseOverviewService.mapConversationsToSidebarCardElements(this.course!, this.conversationsOfUser); + this.accordionConversationGroups = this.courseOverviewService.groupConversationsByChannelType(this.course!, this.conversationsOfUser, this.messagingEnabled); + const currentConversations = this.sidebarConversations?.filter((item) => item.isCurrent) || []; + this.accordionConversationGroups.recently.entityData = currentConversations; this.updateSidebarData(); }, }); diff --git a/src/main/webapp/app/overview/course-overview.service.ts b/src/main/webapp/app/overview/course-overview.service.ts index 97b42ff6dc3f..5f0d648a3b94 100644 --- a/src/main/webapp/app/overview/course-overview.service.ts +++ b/src/main/webapp/app/overview/course-overview.service.ts @@ -20,6 +20,7 @@ import { isGroupChatDTO } from 'app/entities/metis/conversation/group-chat.model import { ConversationService } from 'app/shared/metis/conversations/conversation.service'; import { StudentExam } from 'app/entities/student-exam.model'; import { SavedPostStatusMap } from 'app/entities/metis/posting.model'; +import { Course } from 'app/entities/course.model'; const DEFAULT_UNIT_GROUPS: AccordionGroups = { future: { entityData: [] }, @@ -58,6 +59,7 @@ const GROUP_DECISION_MATRIX: Record { + const aIsFavorite = a.conversation?.isFavorite ? 1 : 0; + const bIsFavorite = b.conversation?.isFavorite ? 1 : 0; + return bIsFavorite - aIsFavorite; + }); + } return groupedConversationGroups; } @@ -273,8 +291,8 @@ export class CourseOverviewService { return exams.map((exam, index) => this.mapExamToSidebarCardElement(exam, studentExams?.[index])); } - mapConversationsToSidebarCardElements(conversations: ConversationDTO[]) { - return conversations.map((conversation) => this.mapConversationToSidebarCardElement(conversation)); + mapConversationsToSidebarCardElements(course: Course, conversations: ConversationDTO[]) { + return conversations.map((conversation) => this.mapConversationToSidebarCardElement(course, conversation)); } mapLectureToSidebarCardElement(lecture: Lecture): SidebarCardElement { @@ -349,7 +367,26 @@ export class CourseOverviewService { } } - mapConversationToSidebarCardElement(conversation: ConversationDTO): SidebarCardElement { + mapConversationToSidebarCardElement(course: Course, conversation: ConversationDTO): SidebarCardElement { + let isCurrent = false; + const channelDTO = getAsChannelDTO(conversation); + const subTypeRefId = channelDTO?.subTypeReferenceId; + const now = dayjs(); + const oneAndHalfWeekBefore = now.subtract(1.5, 'week'); + const oneAndHalfWeekLater = now.add(1.5, 'week'); + let dueDate = null; + if (subTypeRefId && course.exercises && channelDTO?.subType === 'exercise') { + const exercise = course.exercises.find((exercise) => exercise.id === subTypeRefId); + dueDate = exercise?.dueDate || null; + } else if (subTypeRefId && course.lectures && channelDTO?.subType === 'lecture') { + const lecture = course.lectures.find((lecture) => lecture.id === subTypeRefId); + dueDate = lecture?.startDate || null; + } else if (subTypeRefId && course.exams && channelDTO?.subType === 'exam') { + const exam = course.exams.find((exam) => exam.id === subTypeRefId); + dueDate = exam?.startDate || null; + } + isCurrent = dueDate ? dayjs(dueDate).isBetween(oneAndHalfWeekBefore, oneAndHalfWeekLater, 'day', '[]') : false; + const conversationCardItem: SidebarCardElement = { title: this.conversationService.getConversationName(conversation) ?? '', id: conversation.id ?? '', @@ -357,6 +394,7 @@ export class CourseOverviewService { icon: this.getChannelIcon(conversation), conversation: conversation, size: 'S', + isCurrent: isCurrent, }; return conversationCardItem; } diff --git a/src/main/webapp/app/shared/sidebar/sidebar.component.ts b/src/main/webapp/app/shared/sidebar/sidebar.component.ts index f3ea292820bb..4c8592236464 100644 --- a/src/main/webapp/app/shared/sidebar/sidebar.component.ts +++ b/src/main/webapp/app/shared/sidebar/sidebar.component.ts @@ -3,7 +3,7 @@ import { faFilter, faFilterCircleXmark, faHashtag, faPeopleGroup, faPlusCircle, import { ActivatedRoute, Params } from '@angular/router'; import { Subscription, distinctUntilChanged } from 'rxjs'; import { ProfileService } from '../layouts/profiles/profile.service'; -import { ChannelAccordionShowAdd, ChannelTypeIcons, CollapseState, SidebarCardSize, SidebarData, SidebarItemShowAlways, SidebarTypes } from 'app/types/sidebar'; +import { ChannelTypeIcons, CollapseState, SidebarCardSize, SidebarData, SidebarItemShowAlways, SidebarTypes } from 'app/types/sidebar'; import { SidebarEventService } from './sidebar-event.service'; import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; import { cloneDeep } from 'lodash-es'; @@ -32,7 +32,6 @@ export class SidebarComponent implements OnDestroy, OnChanges, OnInit { @Input() sidebarData: SidebarData; @Input() courseId?: number; @Input() itemSelected?: boolean; - @Input() showAddOption?: ChannelAccordionShowAdd; @Input() channelTypeIcon?: ChannelTypeIcons; @Input() collapseState: CollapseState; sidebarItemAlwaysShow = input.required(); diff --git a/src/main/webapp/app/types/sidebar.ts b/src/main/webapp/app/types/sidebar.ts index 48180c03ebfb..c3a0b5faabee 100644 --- a/src/main/webapp/app/types/sidebar.ts +++ b/src/main/webapp/app/types/sidebar.ts @@ -16,6 +16,7 @@ export type AccordionGroups = Record< >; export type ChannelGroupCategory = | 'favoriteChannels' + | 'recently' | 'generalChannels' | 'exerciseChannels' | 'lectureChannels' @@ -27,7 +28,6 @@ export type ChannelGroupCategory = export type CollapseState = { [key: string]: boolean; } & (Record | Record | Record | Record); -export type ChannelAccordionShowAdd = Record; export type ChannelTypeIcons = Record; export type SidebarItemShowAlways = { [key: string]: boolean; @@ -135,4 +135,6 @@ export interface SidebarCardElement { * Set for Conversation. Will be removed after refactoring */ conversation?: ConversationDTO; + + isCurrent?: boolean; } diff --git a/src/main/webapp/i18n/de/student-dashboard.json b/src/main/webapp/i18n/de/student-dashboard.json index 642af5f892fc..4d8efbed2004 100644 --- a/src/main/webapp/i18n/de/student-dashboard.json +++ b/src/main/webapp/i18n/de/student-dashboard.json @@ -82,6 +82,7 @@ "createDirectChat": "Direkt-Chat erstellen", "groupChats": "Gruppenchats", "directMessages": "Direktnachrichten", + "recently": "Kürzlich", "filterConversationPlaceholder": "Konversationen filtern" }, "menu": { diff --git a/src/main/webapp/i18n/en/student-dashboard.json b/src/main/webapp/i18n/en/student-dashboard.json index eb79ff327373..6a6fa4be8337 100644 --- a/src/main/webapp/i18n/en/student-dashboard.json +++ b/src/main/webapp/i18n/en/student-dashboard.json @@ -82,6 +82,7 @@ "createDirectChat": "Create direct chat", "groupChats": "Group Chats", "directMessages": "Direct Messages", + "recently": "Recently", "filterConversationPlaceholder": "Filter conversations" }, "menu": { diff --git a/src/test/javascript/spec/component/course/course-overview.service.spec.ts b/src/test/javascript/spec/component/course/course-overview.service.spec.ts index 728d0fb269a8..cd902510bb92 100644 --- a/src/test/javascript/spec/component/course/course-overview.service.spec.ts +++ b/src/test/javascript/spec/component/course/course-overview.service.spec.ts @@ -429,7 +429,7 @@ describe('CourseOverviewService', () => { jest.spyOn(service, 'getCorrespondingChannelSubType'); jest.spyOn(service, 'mapConversationToSidebarCardElement'); - const groupedConversations = service.groupConversationsByChannelType(conversations, true); + const groupedConversations = service.groupConversationsByChannelType(course, conversations, true); expect(groupedConversations['generalChannels'].entityData).toHaveLength(1); expect(groupedConversations['examChannels'].entityData).toHaveLength(1); @@ -445,7 +445,7 @@ describe('CourseOverviewService', () => { jest.spyOn(service, 'getCorrespondingChannelSubType'); jest.spyOn(service, 'mapConversationToSidebarCardElement'); - const groupedConversations = service.groupConversationsByChannelType(conversations, true); + const groupedConversations = service.groupConversationsByChannelType(course, conversations, true); expect(groupedConversations['generalChannels'].entityData).toHaveLength(2); expect(service.mapConversationToSidebarCardElement).toHaveBeenCalledTimes(2); @@ -460,21 +460,39 @@ describe('CourseOverviewService', () => { jest.spyOn(service, 'mapConversationToSidebarCardElement'); jest.spyOn(service, 'getConversationGroup'); jest.spyOn(service, 'getCorrespondingChannelSubType'); - const groupedConversations = service.groupConversationsByChannelType(conversations, true); + const groupedConversations = service.groupConversationsByChannelType(course, conversations, true); - expect(groupedConversations['generalChannels'].entityData).toHaveLength(2); + expect(groupedConversations['generalChannels'].entityData).toHaveLength(4); expect(groupedConversations['examChannels'].entityData).toHaveLength(1); expect(groupedConversations['exerciseChannels'].entityData).toHaveLength(1); expect(groupedConversations['favoriteChannels'].entityData).toHaveLength(1); expect(groupedConversations['hiddenChannels'].entityData).toHaveLength(1); expect(service.mapConversationToSidebarCardElement).toHaveBeenCalledTimes(6); expect(service.getConversationGroup).toHaveBeenCalledTimes(6); - expect(service.getCorrespondingChannelSubType).toHaveBeenCalledTimes(4); - expect(getAsChannelDTO(groupedConversations['generalChannels'].entityData[0].conversation)?.name).toBe('General'); - expect(getAsChannelDTO(groupedConversations['generalChannels'].entityData[1].conversation)?.name).toBe('General 2'); + expect(service.getCorrespondingChannelSubType).toHaveBeenCalledTimes(6); + expect(getAsChannelDTO(groupedConversations['generalChannels'].entityData[0].conversation)?.name).toBe('fav-channel'); + expect(getAsChannelDTO(groupedConversations['generalChannels'].entityData[1].conversation)?.name).toBe('General'); + expect(getAsChannelDTO(groupedConversations['generalChannels'].entityData[2].conversation)?.name).toBe('General 2'); expect(getAsChannelDTO(groupedConversations['examChannels'].entityData[0].conversation)?.name).toBe('exam-test'); expect(getAsChannelDTO(groupedConversations['exerciseChannels'].entityData[0].conversation)?.name).toBe('exercise-test'); expect(getAsChannelDTO(groupedConversations['favoriteChannels'].entityData[0].conversation)?.name).toBe('fav-channel'); expect(getAsChannelDTO(groupedConversations['hiddenChannels'].entityData[0].conversation)?.name).toBe('hidden-channel'); }); + + it('should not remove favorite conversations from their original section but keep them at the top of the related section', () => { + const conversations = [generalChannel, examChannel, exerciseChannel, favoriteChannel]; + + jest.spyOn(service, 'getCorrespondingChannelSubType'); + jest.spyOn(service, 'mapConversationToSidebarCardElement'); + jest.spyOn(service, 'getConversationGroup'); + const groupedConversations = service.groupConversationsByChannelType(course, conversations, true); + + expect(groupedConversations['favoriteChannels'].entityData).toContainEqual(expect.objectContaining({ id: favoriteChannel.id })); + + expect(groupedConversations['generalChannels'].entityData[0].id).toBe(favoriteChannel.id); + + expect(service.mapConversationToSidebarCardElement).toHaveBeenCalledTimes(4); + expect(service.getConversationGroup).toHaveBeenCalledTimes(4); + expect(service.getCorrespondingChannelSubType).toHaveBeenCalledTimes(4); + }); }); diff --git a/src/test/javascript/spec/component/overview/course-conversations/course-conversations.component.spec.ts b/src/test/javascript/spec/component/overview/course-conversations/course-conversations.component.spec.ts index 825d153b7af7..4cee724dfd94 100644 --- a/src/test/javascript/spec/component/overview/course-conversations/course-conversations.component.spec.ts +++ b/src/test/javascript/spec/component/overview/course-conversations/course-conversations.component.spec.ts @@ -57,6 +57,7 @@ examples.forEach((activeConversation) => { let acceptCodeOfConductSpy: jest.SpyInstance; let setActiveConversationSpy: jest.SpyInstance; let metisConversationService: MetisConversationService; + let courseOverviewService: CourseOverviewService; let modalService: NgbModal; let courseSidebarService: CourseSidebarService; let layoutService: LayoutService; @@ -130,6 +131,7 @@ examples.forEach((activeConversation) => { }); metisConversationService = TestBed.inject(MetisConversationService); + courseOverviewService = TestBed.inject(CourseOverviewService); courseSidebarService = TestBed.inject(CourseSidebarService); layoutService = TestBed.inject(LayoutService); activatedRoute = TestBed.inject(ActivatedRoute); @@ -158,6 +160,39 @@ examples.forEach((activeConversation) => { acceptCodeOfConductSpy = jest.spyOn(metisConversationService, 'acceptCodeOfConduct'); jest.spyOn(metisService, 'posts', 'get').mockReturnValue(postsSubject.asObservable()); modalService = TestBed.inject(NgbModal); + component.sidebarConversations = []; + + jest.spyOn(courseOverviewService, 'mapConversationsToSidebarCardElements').mockReturnValue([ + { + id: 1, + title: 'Test Channel 1', + isCurrent: true, + conversation: { id: 1 }, + size: 'S', + }, + { + id: 2, + title: 'Test Channel 2', + isCurrent: false, + conversation: { id: 2 }, + size: 'S', + }, + ]); + + jest.spyOn(courseOverviewService, 'groupConversationsByChannelType').mockReturnValue({ + recently: { + entityData: [ + { + id: 1, + title: 'Test Channel 1', + isCurrent: true, + conversation: { id: 1 }, + size: 'S', + }, + ], + }, + generalChannels: { entityData: [] }, + }); })); afterEach(() => { @@ -433,6 +468,17 @@ examples.forEach((activeConversation) => { // Since createChannelFn is undefined, prepareSidebarData should not be called expect(prepareSidebarDataSpy).not.toHaveBeenCalled(); }); + + it('should correctly populate the recently group in accordionConversationGroups using existing mocks', fakeAsync(() => { + (metisConversationService.forceRefresh as jest.Mock).mockReturnValue(of({})); + + component.prepareSidebarData(); + tick(); + const recentlyGroup = component.accordionConversationGroups.recently; + expect(recentlyGroup).toBeDefined(); + expect(recentlyGroup.entityData).toHaveLength(1); + expect(recentlyGroup.entityData[0].isCurrent).toBeTrue(); + })); }); describe('query parameter handling', () => { From 051489f2a19f17f511c683d63b47628e8d3ded43 Mon Sep 17 00:00:00 2001 From: Asli Aykan Date: Mon, 16 Dec 2024 22:51:16 +0300 Subject: [PATCH 2/5] fix hidden channel logic --- src/main/webapp/app/overview/course-overview.service.ts | 8 +++++--- .../spec/component/course/course-overview.service.spec.ts | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main/webapp/app/overview/course-overview.service.ts b/src/main/webapp/app/overview/course-overview.service.ts index 5f0d648a3b94..5f4e632d33f8 100644 --- a/src/main/webapp/app/overview/course-overview.service.ts +++ b/src/main/webapp/app/overview/course-overview.service.ts @@ -174,11 +174,13 @@ export class CourseOverviewService { getConversationGroup(conversation: ConversationDTO): ChannelGroupCategory[] { const groups: ChannelGroupCategory[] = []; - if (conversation.isFavorite) { - groups.push('favoriteChannels'); - } if (conversation.isHidden) { groups.push('hiddenChannels'); + return groups; + } + + if (conversation.isFavorite) { + groups.push('favoriteChannels'); } if (isGroupChatDTO(conversation)) { diff --git a/src/test/javascript/spec/component/course/course-overview.service.spec.ts b/src/test/javascript/spec/component/course/course-overview.service.spec.ts index cd902510bb92..683ee4392ad2 100644 --- a/src/test/javascript/spec/component/course/course-overview.service.spec.ts +++ b/src/test/javascript/spec/component/course/course-overview.service.spec.ts @@ -462,14 +462,14 @@ describe('CourseOverviewService', () => { jest.spyOn(service, 'getCorrespondingChannelSubType'); const groupedConversations = service.groupConversationsByChannelType(course, conversations, true); - expect(groupedConversations['generalChannels'].entityData).toHaveLength(4); + expect(groupedConversations['generalChannels'].entityData).toHaveLength(3); expect(groupedConversations['examChannels'].entityData).toHaveLength(1); expect(groupedConversations['exerciseChannels'].entityData).toHaveLength(1); expect(groupedConversations['favoriteChannels'].entityData).toHaveLength(1); expect(groupedConversations['hiddenChannels'].entityData).toHaveLength(1); expect(service.mapConversationToSidebarCardElement).toHaveBeenCalledTimes(6); expect(service.getConversationGroup).toHaveBeenCalledTimes(6); - expect(service.getCorrespondingChannelSubType).toHaveBeenCalledTimes(6); + expect(service.getCorrespondingChannelSubType).toHaveBeenCalledTimes(5); expect(getAsChannelDTO(groupedConversations['generalChannels'].entityData[0].conversation)?.name).toBe('fav-channel'); expect(getAsChannelDTO(groupedConversations['generalChannels'].entityData[1].conversation)?.name).toBe('General'); expect(getAsChannelDTO(groupedConversations['generalChannels'].entityData[2].conversation)?.name).toBe('General 2'); From a010141b8079deb8e9713702a3c50282c7a35e1d Mon Sep 17 00:00:00 2001 From: Asli Aykan Date: Mon, 16 Dec 2024 23:52:31 +0300 Subject: [PATCH 3/5] add test --- .../course/course-overview.service.spec.ts | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/src/test/javascript/spec/component/course/course-overview.service.spec.ts b/src/test/javascript/spec/component/course/course-overview.service.spec.ts index 683ee4392ad2..1c5d1ff43b30 100644 --- a/src/test/javascript/spec/component/course/course-overview.service.spec.ts +++ b/src/test/javascript/spec/component/course/course-overview.service.spec.ts @@ -4,7 +4,6 @@ import { ModelingExercise } from 'app/entities/modeling-exercise.model'; import { Exercise } from 'app/entities/exercise.model'; import { UMLDiagramType } from '@ls1intum/apollon'; import { Course } from 'app/entities/course.model'; -import dayjs from 'dayjs/esm'; import { Lecture } from 'app/entities/lecture.model'; import { provideHttpClientTesting } from '@angular/common/http/testing'; import { TranslateService } from '@ngx-translate/core'; @@ -15,6 +14,9 @@ import { TextExercise } from 'app/entities/text/text-exercise.model'; import { Exam } from 'app/entities/exam/exam.model'; import { ChannelDTO, ChannelSubType, getAsChannelDTO } from 'app/entities/metis/conversation/channel.model'; import { provideHttpClient } from '@angular/common/http'; +import isBetween from 'dayjs/plugin/isBetween'; +import dayjs from 'dayjs'; +import { ConversationDTO, ConversationType } from 'app/entities/metis/conversation/conversation.model'; describe('CourseOverviewService', () => { let service: CourseOverviewService; @@ -56,6 +58,7 @@ describe('CourseOverviewService', () => { course = new Course(); course.id = 1; + dayjs.extend(isBetween); pastExercise = new ModelingExercise(UMLDiagramType.ClassDiagram, course, undefined) as Exercise; pastExercise.dueDate = lastWeek; @@ -495,4 +498,34 @@ describe('CourseOverviewService', () => { expect(service.getConversationGroup).toHaveBeenCalledTimes(4); expect(service.getCorrespondingChannelSubType).toHaveBeenCalledTimes(4); }); + + it('should correctly set isCurrent based on the date range in mapConversationToSidebarCardElement', () => { + const now = dayjs(); + const oneAndHalfWeekBefore = now.subtract(1.5, 'week'); + + const conversationWithinRange = { + id: 5, + subType: ChannelSubType.EXERCISE, + subTypeReferenceId: 101, + type: ConversationType.CHANNEL, + } as ConversationDTO; + + const conversationOutsideRange = { + subType: ChannelSubType.LECTURE, + subTypeReferenceId: 102, + type: ConversationType.CHANNEL, + } as ConversationDTO; + + const exerciseWithinRange = { id: 101, dueDate: oneAndHalfWeekBefore.add(3, 'day') } as unknown as Exercise; + const lectureOutsideRange = { id: 102, startDate: oneAndHalfWeekBefore.subtract(1, 'day') } as unknown as Lecture; + + course.exercises = [exerciseWithinRange]; + course.lectures = [lectureOutsideRange]; + + const sidebarCardWithinRange = service.mapConversationToSidebarCardElement(course, conversationWithinRange); + const sidebarCardOutsideRange = service.mapConversationToSidebarCardElement(course, conversationOutsideRange); + + expect(sidebarCardWithinRange.isCurrent).toBeTrue(); + expect(sidebarCardOutsideRange.isCurrent).toBeFalse(); + }); }); From a3e27b24d1f09eccc9c471992ff560800a66d2bd Mon Sep 17 00:00:00 2001 From: Asli Aykan Date: Mon, 16 Dec 2024 23:55:58 +0300 Subject: [PATCH 4/5] update test --- .../spec/component/course/course-overview.service.spec.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/test/javascript/spec/component/course/course-overview.service.spec.ts b/src/test/javascript/spec/component/course/course-overview.service.spec.ts index 1c5d1ff43b30..26b6028a6bf6 100644 --- a/src/test/javascript/spec/component/course/course-overview.service.spec.ts +++ b/src/test/javascript/spec/component/course/course-overview.service.spec.ts @@ -14,8 +14,7 @@ import { TextExercise } from 'app/entities/text/text-exercise.model'; import { Exam } from 'app/entities/exam/exam.model'; import { ChannelDTO, ChannelSubType, getAsChannelDTO } from 'app/entities/metis/conversation/channel.model'; import { provideHttpClient } from '@angular/common/http'; -import isBetween from 'dayjs/plugin/isBetween'; -import dayjs from 'dayjs'; +import dayjs from 'dayjs/esm'; import { ConversationDTO, ConversationType } from 'app/entities/metis/conversation/conversation.model'; describe('CourseOverviewService', () => { @@ -58,7 +57,6 @@ describe('CourseOverviewService', () => { course = new Course(); course.id = 1; - dayjs.extend(isBetween); pastExercise = new ModelingExercise(UMLDiagramType.ClassDiagram, course, undefined) as Exercise; pastExercise.dueDate = lastWeek; From 812002c9d849d898fc82ec2ad57e470984e05ef0 Mon Sep 17 00:00:00 2001 From: Asli Aykan Date: Fri, 20 Dec 2024 02:01:34 +0300 Subject: [PATCH 5/5] add releaseDate check for exercise, rename recently to recents --- .../course-conversations.component.ts | 10 +++++----- .../webapp/app/overview/course-overview.service.ts | 14 ++++++++------ src/main/webapp/app/types/sidebar.ts | 2 +- src/main/webapp/i18n/de/student-dashboard.json | 2 +- src/main/webapp/i18n/en/student-dashboard.json | 2 +- .../course-conversations.component.spec.ts | 12 ++++++------ 6 files changed, 22 insertions(+), 20 deletions(-) diff --git a/src/main/webapp/app/overview/course-conversations/course-conversations.component.ts b/src/main/webapp/app/overview/course-conversations/course-conversations.component.ts index 57ea79fc6372..96f9fd1544ce 100644 --- a/src/main/webapp/app/overview/course-conversations/course-conversations.component.ts +++ b/src/main/webapp/app/overview/course-conversations/course-conversations.component.ts @@ -45,7 +45,7 @@ import { canCreateChannel } from 'app/shared/metis/conversations/conversation-pe const DEFAULT_CHANNEL_GROUPS: AccordionGroups = { favoriteChannels: { entityData: [] }, - recently: { entityData: [] }, + recents: { entityData: [] }, generalChannels: { entityData: [] }, exerciseChannels: { entityData: [] }, lectureChannels: { entityData: [] }, @@ -64,7 +64,7 @@ const CHANNEL_TYPE_ICON: ChannelTypeIcons = { lectureChannels: faFile, hiddenChannels: faBan, savedPosts: faBookmark, - recently: faClock, + recents: faClock, }; const DEFAULT_COLLAPSE_STATE: CollapseState = { @@ -77,7 +77,7 @@ const DEFAULT_COLLAPSE_STATE: CollapseState = { lectureChannels: true, hiddenChannels: true, savedPosts: true, - recently: true, + recents: true, }; const DEFAULT_SHOW_ALWAYS: SidebarItemShowAlways = { @@ -90,7 +90,7 @@ const DEFAULT_SHOW_ALWAYS: SidebarItemShowAlways = { lectureChannels: false, hiddenChannels: false, savedPosts: true, - recently: true, + recents: true, }; @Component({ @@ -404,7 +404,7 @@ export class CourseConversationsComponent implements OnInit, OnDestroy { this.sidebarConversations = this.courseOverviewService.mapConversationsToSidebarCardElements(this.course!, this.conversationsOfUser); this.accordionConversationGroups = this.courseOverviewService.groupConversationsByChannelType(this.course!, this.conversationsOfUser, this.messagingEnabled); const currentConversations = this.sidebarConversations?.filter((item) => item.isCurrent) || []; - this.accordionConversationGroups.recently.entityData = currentConversations; + this.accordionConversationGroups.recents.entityData = currentConversations; this.updateSidebarData(); }, }); diff --git a/src/main/webapp/app/overview/course-overview.service.ts b/src/main/webapp/app/overview/course-overview.service.ts index 5f4e632d33f8..6e13440242bb 100644 --- a/src/main/webapp/app/overview/course-overview.service.ts +++ b/src/main/webapp/app/overview/course-overview.service.ts @@ -59,7 +59,7 @@ const GROUP_DECISION_MATRIX: Record exercise.id === subTypeRefId); - dueDate = exercise?.dueDate || null; + const relevantDates = [exercise?.releaseDate, exercise?.dueDate].filter(Boolean); + isCurrent = relevantDates.some((date) => dayjs(date).isBetween(oneAndHalfWeekBefore, oneAndHalfWeekLater, 'day', '[]')); } else if (subTypeRefId && course.lectures && channelDTO?.subType === 'lecture') { const lecture = course.lectures.find((lecture) => lecture.id === subTypeRefId); - dueDate = lecture?.startDate || null; + relevantDate = lecture?.startDate || null; + isCurrent = relevantDate ? dayjs(relevantDate).isBetween(oneAndHalfWeekBefore, oneAndHalfWeekLater, 'day', '[]') : false; } else if (subTypeRefId && course.exams && channelDTO?.subType === 'exam') { const exam = course.exams.find((exam) => exam.id === subTypeRefId); - dueDate = exam?.startDate || null; + relevantDate = exam?.startDate || null; + isCurrent = relevantDate ? dayjs(relevantDate).isBetween(oneAndHalfWeekBefore, oneAndHalfWeekLater, 'day', '[]') : false; } - isCurrent = dueDate ? dayjs(dueDate).isBetween(oneAndHalfWeekBefore, oneAndHalfWeekLater, 'day', '[]') : false; const conversationCardItem: SidebarCardElement = { title: this.conversationService.getConversationName(conversation) ?? '', diff --git a/src/main/webapp/app/types/sidebar.ts b/src/main/webapp/app/types/sidebar.ts index c3a0b5faabee..7143b45b9298 100644 --- a/src/main/webapp/app/types/sidebar.ts +++ b/src/main/webapp/app/types/sidebar.ts @@ -16,7 +16,7 @@ export type AccordionGroups = Record< >; export type ChannelGroupCategory = | 'favoriteChannels' - | 'recently' + | 'recents' | 'generalChannels' | 'exerciseChannels' | 'lectureChannels' diff --git a/src/main/webapp/i18n/de/student-dashboard.json b/src/main/webapp/i18n/de/student-dashboard.json index 4d8efbed2004..47b4fe4ab92f 100644 --- a/src/main/webapp/i18n/de/student-dashboard.json +++ b/src/main/webapp/i18n/de/student-dashboard.json @@ -82,7 +82,7 @@ "createDirectChat": "Direkt-Chat erstellen", "groupChats": "Gruppenchats", "directMessages": "Direktnachrichten", - "recently": "Kürzlich", + "recents": "Kürzliches", "filterConversationPlaceholder": "Konversationen filtern" }, "menu": { diff --git a/src/main/webapp/i18n/en/student-dashboard.json b/src/main/webapp/i18n/en/student-dashboard.json index 6a6fa4be8337..065b6525da83 100644 --- a/src/main/webapp/i18n/en/student-dashboard.json +++ b/src/main/webapp/i18n/en/student-dashboard.json @@ -82,7 +82,7 @@ "createDirectChat": "Create direct chat", "groupChats": "Group Chats", "directMessages": "Direct Messages", - "recently": "Recently", + "recents": "Recents", "filterConversationPlaceholder": "Filter conversations" }, "menu": { diff --git a/src/test/javascript/spec/component/overview/course-conversations/course-conversations.component.spec.ts b/src/test/javascript/spec/component/overview/course-conversations/course-conversations.component.spec.ts index 4cee724dfd94..2ff1f74e967a 100644 --- a/src/test/javascript/spec/component/overview/course-conversations/course-conversations.component.spec.ts +++ b/src/test/javascript/spec/component/overview/course-conversations/course-conversations.component.spec.ts @@ -180,7 +180,7 @@ examples.forEach((activeConversation) => { ]); jest.spyOn(courseOverviewService, 'groupConversationsByChannelType').mockReturnValue({ - recently: { + recents: { entityData: [ { id: 1, @@ -469,15 +469,15 @@ examples.forEach((activeConversation) => { expect(prepareSidebarDataSpy).not.toHaveBeenCalled(); }); - it('should correctly populate the recently group in accordionConversationGroups using existing mocks', fakeAsync(() => { + it('should correctly populate the recents group in accordionConversationGroups using existing mocks', fakeAsync(() => { (metisConversationService.forceRefresh as jest.Mock).mockReturnValue(of({})); component.prepareSidebarData(); tick(); - const recentlyGroup = component.accordionConversationGroups.recently; - expect(recentlyGroup).toBeDefined(); - expect(recentlyGroup.entityData).toHaveLength(1); - expect(recentlyGroup.entityData[0].isCurrent).toBeTrue(); + const recentsGroup = component.accordionConversationGroups.recents; + expect(recentsGroup).toBeDefined(); + expect(recentsGroup.entityData).toHaveLength(1); + expect(recentsGroup.entityData[0].isCurrent).toBeTrue(); })); });