From a495eed7ecb2bf42d4fb336648f99c3c7ea6bcb7 Mon Sep 17 00:00:00 2001 From: Mutugi <48474421+Mutugiii@users.noreply.github.com> Date: Thu, 4 Jan 2024 21:43:26 +0300 Subject: [PATCH 1/3] courses: improve data consistency (fixes #7362) (#7367) Co-authored-by: dogi --- package.json | 4 ++-- src/app/courses/add-courses/courses-add.component.html | 4 ++-- src/app/courses/add-courses/courses-add.component.ts | 4 ++-- .../courses/view-courses/courses-view-detail.component.html | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 677ec814d8..0ff6d06d21 100755 --- a/package.json +++ b/package.json @@ -1,9 +1,9 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.14.6", + "version": "0.14.7", "myplanet": { - "latest": "v0.12.59", + "latest": "v0.12.63", "min": "v0.12.0" }, "scripts": { diff --git a/src/app/courses/add-courses/courses-add.component.html b/src/app/courses/add-courses/courses-add.component.html index 3a4aa9c6d9..66b966ec94 100644 --- a/src/app/courses/add-courses/courses-add.component.html +++ b/src/app/courses/add-courses/courses-add.component.html @@ -26,12 +26,12 @@ - + {{grade.label}} - + {{sub.label}} diff --git a/src/app/courses/add-courses/courses-add.component.ts b/src/app/courses/add-courses/courses-add.component.ts index e50741d9e0..1009f1488b 100644 --- a/src/app/courses/add-courses/courses-add.component.ts +++ b/src/app/courses/add-courses/courses-add.component.ts @@ -89,8 +89,8 @@ export class CoursesAddComponent implements OnInit, OnDestroy { ], description: [ '', CustomValidators.requiredMarkdown ], languageOfInstruction: '', - gradeLevel: this.gradeLevels[0], - subjectLevel: this.subjectLevels[0], + gradeLevel: '', + subjectLevel: '', createdDate: this.couchService.datePlaceholder, creator: this.userService.get().name + '@' + configuration.code, sourcePlanet: configuration.code, diff --git a/src/app/courses/view-courses/courses-view-detail.component.html b/src/app/courses/view-courses/courses-view-detail.component.html index a4180bbd80..c0f14d6783 100644 --- a/src/app/courses/view-courses/courses-view-detail.component.html +++ b/src/app/courses/view-courses/courses-view-detail.component.html @@ -1,7 +1,7 @@

Subject Level:

Grade Level:

-

Language of Instruction:

+

Language of Instruction:

Creator: {{courseDetail?.creatorDoc?.fullName}}

Source: {{courseDetail?.sourcePlanet}}

Description:

From 9bf11c3090ba2d84bdfd6a0f9f790eb673070b39 Mon Sep 17 00:00:00 2001 From: Mutugi <48474421+Mutugiii@users.noreply.github.com> Date: Tue, 9 Jan 2024 05:09:01 +0300 Subject: [PATCH 2/3] login: refactor archive functions (fixes #7397) (#7398) Co-authored-by: dogi --- package.json | 4 +- src/app/login/login-form.component.ts | 63 +++++++++++-------- .../users-archive/users-archive.component.ts | 16 ++--- 3 files changed, 48 insertions(+), 35 deletions(-) diff --git a/package.json b/package.json index 0ff6d06d21..4ad43dcf6f 100755 --- a/package.json +++ b/package.json @@ -1,9 +1,9 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.14.7", + "version": "0.14.8", "myplanet": { - "latest": "v0.12.63", + "latest": "v0.12.72", "min": "v0.12.0" }, "scripts": { diff --git a/src/app/login/login-form.component.ts b/src/app/login/login-form.component.ts index 15d2ff6900..1673ca185b 100644 --- a/src/app/login/login-form.component.ts +++ b/src/app/login/login-form.component.ts @@ -141,39 +141,50 @@ export class LoginFormComponent { { withCredentials: true, domain: this.stateService.configuration.parentDomain }); } - checkArchiveStatus(name) { - this.couchService.get('_users/org.couchdb.user:' + name).subscribe((userData) => { + async checkArchiveStatus(name) { + try { + const userData = await this.couchService.get('_users/org.couchdb.user:' + name).toPromise(); + if (userData?.isArchived) { - this.errorHandler($localize`Member ${name} is not registered`)(); + this.errorHandler($localize`Member ${name} is not registered.`)(); + return true; } - }); - return true; + return false; + } catch (error) { + this.errorHandler($localize`There was an error connecting to Planet`)(); + return false; + } } - login({ name, password }: { name: string, password: string }, isCreate: boolean) { + async login({ name, password }: { name: string, password: string }, isCreate: boolean) { const configuration = this.stateService.configuration; const userId = `org.couchdb.user:${name}`; - if (this.checkArchiveStatus(name)) { - return; + + try { + if (await this.checkArchiveStatus(name)) { + return; + } + this.pouchAuthService.login(name, password).pipe( + switchMap(() => isCreate ? from(this.router.navigate([ 'users/update/' + name ])) : from(this.reRoute())), + switchMap(() => forkJoin(this.pouchService.replicateFromRemoteDBs())), + switchMap(this.createSession(name, password)), + switchMap((sessionData) => { + const adminName = configuration.adminName.split('@')[0]; + return isCreate ? this.sendNotifications(adminName, name) : of(sessionData); + }), + switchMap(() => this.submissionsService.getSubmissions(findDocuments({ type: 'survey', status: 'pending', 'user.name': name }))), + map((surveys) => { + const uniqueSurveys = dedupeObjectArray(surveys, [ 'parentId' ]); + if (uniqueSurveys.length > 0) { + this.openNotificationsDialog(uniqueSurveys); + } + }), + switchMap(() => this.healthService.userHealthSecurity(this.healthService.userDatabaseName(userId))), + catchError(error => error.status === 404 ? of({}) : throwError(error)) + ).subscribe(() => {}, this.loginError.bind(this)); + } catch (error) { + console.error('Error during login:', error); } - this.pouchAuthService.login(name, password).pipe( - switchMap(() => isCreate ? from(this.router.navigate([ 'users/update/' + name ])) : from(this.reRoute())), - switchMap(() => forkJoin(this.pouchService.replicateFromRemoteDBs())), - switchMap(this.createSession(name, password)), - switchMap((sessionData) => { - const adminName = configuration.adminName.split('@')[0]; - return isCreate ? this.sendNotifications(adminName, name) : of(sessionData); - }), - switchMap(() => this.submissionsService.getSubmissions(findDocuments({ type: 'survey', status: 'pending', 'user.name': name }))), - map((surveys) => { - const uniqueSurveys = dedupeObjectArray(surveys, [ 'parentId' ]); - if (uniqueSurveys.length > 0) { - this.openNotificationsDialog(uniqueSurveys); - } - }), - switchMap(() => this.healthService.userHealthSecurity(this.healthService.userDatabaseName(userId))), - catchError(error => error.status === 404 ? of({}) : throwError(error)) - ).subscribe(() => {}, this.loginError.bind(this)); } loginError() { diff --git a/src/app/users/users-archive/users-archive.component.ts b/src/app/users/users-archive/users-archive.component.ts index edfb08dcdd..ce0ceb3322 100644 --- a/src/app/users/users-archive/users-archive.component.ts +++ b/src/app/users/users-archive/users-archive.component.ts @@ -1,4 +1,4 @@ -import { Component, EventEmitter, OnInit, Output } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; import { FormBuilder, FormGroup } from '@angular/forms'; import { CouchService } from '../../shared/couchdb.service'; @@ -52,12 +52,14 @@ export class UsersArchiveComponent implements OnInit { archiveUser() { const description = this.archiveForm.get('description').value; this.user = { ...this.user, isArchived: true, archiveReason: description }; - this.userService.updateUser(this.user).subscribe(() => { - this.userService.setUserLogout(); - this.spinnerOn = false; - }, (err) => { + this.userService.updateUser(this.user).subscribe( + () => { + this.userService.setUserLogout(); + this.spinnerOn = false; + }, + (err) => { console.log(err); - }); + } + ); } - } From 08518d97358fcddb2319b55857b9a57b9350edc3 Mon Sep 17 00:00:00 2001 From: Mutugi <48474421+Mutugiii@users.noreply.github.com> Date: Thu, 11 Jan 2024 22:46:23 +0300 Subject: [PATCH 3/3] chat: refactor (fixes #7401) (#7404) Co-authored-by: dogi --- package.json | 4 +- .../chat-sidebar/chat-sidebar.component.html | 2 +- .../chat-sidebar/chat-sidebar.component.ts | 58 +++++++++++-------- .../chat/chat-window/chat-window.component.ts | 46 +++++++++------ src/app/chat/chat.component.ts | 2 +- src/app/chat/chat.model.ts | 21 +++++++ 6 files changed, 88 insertions(+), 45 deletions(-) create mode 100644 src/app/chat/chat.model.ts diff --git a/package.json b/package.json index 4ad43dcf6f..222127de8b 100755 --- a/package.json +++ b/package.json @@ -1,9 +1,9 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.14.8", + "version": "0.14.9", "myplanet": { - "latest": "v0.12.72", + "latest": "v0.12.78", "min": "v0.12.0" }, "scripts": { diff --git a/src/app/chat/chat-sidebar/chat-sidebar.component.html b/src/app/chat/chat-sidebar/chat-sidebar.component.html index 5dc39b2d13..889e00e68e 100644 --- a/src/app/chat/chat-sidebar/chat-sidebar.component.html +++ b/src/app/chat/chat-sidebar/chat-sidebar.component.html @@ -8,7 +8,7 @@ - +
diff --git a/src/app/chat/chat-sidebar/chat-sidebar.component.ts b/src/app/chat/chat-sidebar/chat-sidebar.component.ts index 1b85ea15fb..85bfb3c275 100644 --- a/src/app/chat/chat-sidebar/chat-sidebar.component.ts +++ b/src/app/chat/chat-sidebar/chat-sidebar.component.ts @@ -3,6 +3,7 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; +import { Conversation } from '../chat.model'; import { ChatService } from '../../shared/chat.service'; import { CouchService } from '../../shared/couchdb.service'; import { DeviceInfoService, DeviceType } from '../../shared/device-info.service'; @@ -25,9 +26,9 @@ export class ChatSidebarComponent implements OnInit, OnDestroy { this.recordSearch(); this.filterConversations(); } - conversations: any; - filteredConversations: any; - selectedConversation: any; + conversations: Conversation[]; + filteredConversations: Conversation[]; + selectedConversation: Conversation; isEditing: boolean; fullTextSearch = false; searchType: 'questions' | 'responses'; @@ -82,7 +83,7 @@ export class ChatSidebarComponent implements OnInit, OnDestroy { this.overlayOpen = !this.overlayOpen; } - updateConversation(conversation, title) { + updateConversation(conversation: Conversation, title) { this.couchService.updateDocument( this.dbName, { ...conversation, title: title, updatedDate: this.couchService.datePlaceholder } ).subscribe((data) => { @@ -91,7 +92,7 @@ export class ChatSidebarComponent implements OnInit, OnDestroy { }); } - submitTitle(conversation) { + submitTitle(conversation: Conversation) { if (this.titleForm[conversation._id].valid) { const title = this.titleForm[conversation._id].get('title').value; this.updateConversation(conversation, title); @@ -102,7 +103,7 @@ export class ChatSidebarComponent implements OnInit, OnDestroy { } initializeFormGroups() { - this.conversations.forEach((conversation) => { + this.conversations.forEach((conversation: Conversation) => { this.titleForm[conversation._id] = this.formBuilder.group({ title: [ conversation?.title, Validators.required ] }); @@ -125,7 +126,7 @@ export class ChatSidebarComponent implements OnInit, OnDestroy { ); } - selectConversation(conversation) { + selectConversation(conversation: Conversation) { this.selectedConversation = conversation; this.chatService.setSelectedConversationId({ '_id': conversation?._id, @@ -133,8 +134,8 @@ export class ChatSidebarComponent implements OnInit, OnDestroy { }); } - onSearchChange() { - this.titleSearch = this.titleSearch; + onSearchChange(searchValue: string) { + this.titleSearch = searchValue; } resetFilter() { @@ -154,28 +155,37 @@ export class ChatSidebarComponent implements OnInit, OnDestroy { this.filterConversations(); } + matchesSearchTerm(value: string, searchTerm: string): boolean { + return value?.toLowerCase().includes(searchTerm.toLowerCase()); + } + + filterByTitle(conversation: Conversation): boolean { + return this.matchesSearchTerm(conversation.title, this.titleSearch); + } + + filterByFullText(conversation: Conversation): boolean { + return conversation.conversations.some(chat => { + const queryMatch = this.matchesSearchTerm(chat.query, this.titleSearch); + const responseMatch = this.matchesSearchTerm(chat.response, this.titleSearch); + if (this.searchType === 'questions') { + return queryMatch; + } else if (this.searchType === 'responses') { + return responseMatch; + } else { + return queryMatch || responseMatch; + } + }); + } + filterConversations() { if (this.titleSearch.trim() === '' ) { this.getChatHistory(); } - this.filteredConversations = this.conversations?.filter(conversation => { if (this.fullTextSearch) { - const conversationMatches = conversation.conversations.some(chat => { - const queryMatch = chat.query?.toLowerCase().includes(this.titleSearch.toLowerCase()); - const responseMatch = chat.response?.toLowerCase().includes(this.titleSearch.toLowerCase()); - if (this.searchType === 'questions') { - return queryMatch; - } else if (this.searchType === 'responses') { - return responseMatch; - } else { - return queryMatch || responseMatch; - } - }); - return conversationMatches; + return this.filterByFullText(conversation); } - - return conversation.title?.toLowerCase().includes(this.titleSearch.toLowerCase()); + return this.filterByTitle(conversation); }); } } diff --git a/src/app/chat/chat-window/chat-window.component.ts b/src/app/chat/chat-window/chat-window.component.ts index 5655843210..603bbcb5c7 100644 --- a/src/app/chat/chat-window/chat-window.component.ts +++ b/src/app/chat/chat-window/chat-window.component.ts @@ -4,6 +4,7 @@ import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; import { CustomValidators } from '../../validators/custom-validators'; +import { ConversationForm } from '../chat.model'; import { ChatService } from '../../shared/chat.service'; import { CouchService } from '../../shared/couchdb.service'; import { showFormErrors } from '../../shared/table-helpers'; @@ -20,11 +21,11 @@ export class ChatWindowComponent implements OnInit, OnDestroy { conversations: any[] = []; selectedConversationId: any; promptForm: FormGroup; - data = { - user: this.userService.get().name, - content: '', + data: ConversationForm = { _id: '', - _rev: '' + _rev: '', + user: this.userService.get().name, + content: '' }; @ViewChild('chat') chatContainer: ElementRef; @@ -54,6 +55,8 @@ export class ChatWindowComponent implements OnInit, OnDestroy { .subscribe(() => { this.selectedConversationId = null; this.conversations = []; + }, error => { + console.error('Error subscribing to newChatSelected$', error); }); } @@ -63,6 +66,8 @@ export class ChatWindowComponent implements OnInit, OnDestroy { .subscribe((conversationId) => { this.selectedConversationId = conversationId; this.fetchConversation(this.selectedConversationId?._id); + }, error => { + console.error('Error subscribing to selectedConversationId$', error); }); } @@ -74,27 +79,34 @@ export class ChatWindowComponent implements OnInit, OnDestroy { fetchConversation(id) { if (id) { - this.chatService.findConversations([ id ]).subscribe( - (conversation: Object) => { - const messages = conversation[0]?.conversations; - - this.conversations = messages; - } - ); + try { + this.chatService.findConversations([ id ]).subscribe( + (conversation: Object) => { + const messages = conversation[0]?.conversations; + this.conversations = messages; + } + ); + } catch (error) { + console.error('Error fetching conversation: ', error); + } } } - scrollToBottom(): void { + scrollTo(position: 'top' | 'bottom'): void { + const target = position === 'top' ? 0 : this.chatContainer.nativeElement.scrollHeight; this.chatContainer.nativeElement.scrollTo({ - top: this.chatContainer.nativeElement.scrollHeight, + top: target, behavior: 'smooth', }); } - setSelectedConversation() { + setSelectedConversation(): void { if (this.selectedConversationId) { - this.data._id = this.selectedConversationId._id; - this.data._rev = this.selectedConversationId._rev; + this.data = { + ...this.data, + _id: this.selectedConversationId._id, + _rev: this.selectedConversationId._rev, + }; } else { delete this.data._id; delete this.data._rev; @@ -104,7 +116,7 @@ export class ChatWindowComponent implements OnInit, OnDestroy { postSubmit() { this.changeDetectorRef.detectChanges(); this.spinnerOn = true; - this.scrollToBottom(); + this.scrollTo('bottom'); this.promptForm.controls['prompt'].setValue(''); } diff --git a/src/app/chat/chat.component.ts b/src/app/chat/chat.component.ts index 810f9c1963..0eb3f1d288 100644 --- a/src/app/chat/chat.component.ts +++ b/src/app/chat/chat.component.ts @@ -13,7 +13,7 @@ export class ChatComponent { private router: Router, ) {} - goBack() { + goBack(): void { this.router.navigate([ '/' ], { relativeTo: this.route }); } diff --git a/src/app/chat/chat.model.ts b/src/app/chat/chat.model.ts new file mode 100644 index 0000000000..43a2773223 --- /dev/null +++ b/src/app/chat/chat.model.ts @@ -0,0 +1,21 @@ +export interface ConversationForm { + _id: string; + _rev: string; + user: string; + content: string; +} + +export interface Conversation { + _id: string; + _rev: string; + user: string; + conversations: Message[]; + title: string; + createdDate: number; + updatedDate: number; +} + +export interface Message { + query: string; + response: string; +}