Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
f240091
[CORE-6551] handled http429
trtshen May 9, 2024
1e72748
Merge branch 'golive/2.3.2' into task/CORE-6551/http-429-manipulate
trtshen May 9, 2024
2c3ead9
Merge branch 'golive/2.3.2' into task/CORE-6551/http-429-manipulate
trtshen May 14, 2024
8250d29
Merge branch 'golive/2.3.2' into task/CORE-6551/http-429-manipulate
trtshen May 14, 2024
6beb57a
[CORE-6621] chore: Add loading spinner to experiences page
trtshen Jun 13, 2024
248be0b
Merge branch 'prerelease' into task/CORE-6624/experience-page-loading…
trtshen Jun 14, 2024
ed1ddd0
deploy
Jun 15, 2024
f20f337
Merge branch 'prerelease' into release/live
jazzmind Jun 15, 2024
2091a02
Merge branch 'prerelease' into task/CORE-6551/http-429-manipulate
trtshen Jun 18, 2024
3f34090
Merge remote-tracking branch 'origin/prerelease' into release/live
jazzmind Jun 21, 2024
ac73ef2
[CORE-6491] self-recovery or NaN
trtshen Jun 24, 2024
de3975e
Merge pull request #2166 from intersective/bugfix/CORE-6491/question-…
trtshen Jun 27, 2024
68291ed
[CORE-6667] auto-refresh upon newItem pusher event
trtshen Jun 29, 2024
b6ab84d
[CORE-6667] show instant indicator for unlock
trtshen Jun 29, 2024
865103e
Merge pull request #2171 from intersective/hotfix/CORE-6667/refresh-a…
jazzmind Jul 1, 2024
2521403
Merge pull request #2127 from intersective/task/CORE-6551/http-429-ma…
trtshen Jul 1, 2024
6d5adb7
[CORE-6673] revalidate on visit
trtshen Jul 2, 2024
6dd0a64
Merge branch 'prerelease' into task/CORE-6624/experience-page-loading…
trtshen Jul 2, 2024
ca55d46
[CORE-6675] check latest subsmission status before submission - prere…
trtshen Jul 3, 2024
93a3bcc
Merge pull request #2176 from intersective/hotfix/CORE-6675/pull-late…
trtshen Jul 3, 2024
d2e8036
Merge pull request #2179 from intersective/hotfix/CORE-6675/pull-late…
trtshen Jul 4, 2024
ad8489d
[CORE-6241] improved offline detection
trtshen Dec 5, 2023
f7c8826
[CORE-6678] added submission status check for review & mobile view
trtshen Jul 4, 2024
182b574
Merge pull request #2182 from intersective/hotfix/CORE-6241/offline-c…
trtshen Jul 5, 2024
85a3560
Merge branch 'prerelease' into hotfix/CORE-6678/check-submission-stat…
trtshen Jul 9, 2024
4a9831a
[CORE-6680] duplicated toast
trtshen Jul 9, 2024
b7038b1
[CORE-6678] toast msg added
trtshen Jul 10, 2024
cfb1e6f
Merge pull request #2183 from intersective/hotfix/CORE-6678/check-sub…
trtshen Jul 10, 2024
4a06aba
Merge pull request #2173 from intersective/hotfix/CORE-6673/dot-not-c…
trtshen Jul 10, 2024
271f861
Merge branch 'prerelease' into golive/2.3.2.4
trtshen Jul 10, 2024
2a4eb4d
Merge pull request #2190 from intersective/prerelease-golive2324
trtshen Jul 11, 2024
083be1f
[CORE-6686] fixed randomly enabled submit button
trtshen Jul 14, 2024
9e6ce6b
Merge pull request #2192 from intersective/bugfix/CORE-6686/btn-wrong…
jazzmind Jul 15, 2024
8e9ff74
Merge pull request #2189 from intersective/golive/2.3.2.4
jazzmind Jul 15, 2024
10e2afe
Merge pull request #2175 from intersective/task/CORE-6624/experience-…
jazzmind Jul 15, 2024
3dff487
diable old unwanted alarms
Jul 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions projects/request/src/lib/request.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { has, isEmpty, each } from 'lodash';
interface RequestOptions {
headers?: any;
params?: any;
observe?: string;
}

@Injectable({ providedIn: 'root' })
Expand Down Expand Up @@ -127,6 +128,9 @@ export class RequestService {
if (!has(httpOptions, 'params')) {
httpOptions.params = '';
}
if (!has(httpOptions, 'observe')) {
httpOptions.observe = 'body';
}

const request = this.http.get<any>(this.getEndpointUrl(endPoint), {
headers: this.appendHeaders(httpOptions.headers),
Expand Down
49 changes: 34 additions & 15 deletions projects/v3/src/app/components/activity/activity.component.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core';
import { Subject } from 'rxjs';
import { Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, SimpleChanges } from '@angular/core';
import { SharedService } from '@v3/app/services/shared.service';
import { UnlockIndicatorService } from '@v3/app/services/unlock-indicator.service';
import { Activity, ActivityService, Task } from '@v3/services/activity.service';
import { Submission } from '@v3/services/assessment.service';
import { NotificationsService } from '@v3/services/notifications.service';
import { BrowserStorageService } from '@v3/services/storage.service';
import { UtilsService } from '@v3/services/utils.service';
import { takeUntil } from 'rxjs/operators';

@Component({
selector: 'app-activity',
templateUrl: './activity.component.html',
styleUrls: ['./activity.component.scss'],
})
export class ActivityComponent implements OnInit, OnChanges {
export class ActivityComponent implements OnInit, OnChanges, OnDestroy {
@Input() activity: Activity;
@Input() currentTask: Task;
@Input() submission: Submission;
Expand All @@ -25,24 +27,36 @@ export class ActivityComponent implements OnInit, OnChanges {
// false: at least one non-team task
@Output() cannotAccessTeamActivity = new EventEmitter();
isForTeamOnly: boolean = false;
private unsubscribe$: Subject<any> = new Subject();

constructor(
private utils: UtilsService,
private storageService: BrowserStorageService,
private notificationsService: NotificationsService,
private sharedService: SharedService,
private activityService: ActivityService,
private unlockIndicatorService: UnlockIndicatorService,
private unlockIndicatorService: UnlockIndicatorService
) {}

ngOnInit() {
this.leadImage = this.storageService.getUser().programImage;
this.unlockIndicatorService.unlockedTasks$.subscribe((unlockedTasks) => {
this.newTasks = {};
unlockedTasks.forEach((task) => {
ngOnDestroy(): void {
this.unsubscribe$.next();
this.unsubscribe$.complete();
}

resetTaskIndicator(unlockedTasks) {
this.newTasks = {};
unlockedTasks
.filter((task) => task.taskId)
.forEach((task) => {
this.newTasks[task.taskId] = true;
});
});
}

ngOnInit() {
this.leadImage = this.storageService.getUser().programImage;
this.unlockIndicatorService.unlockedTasks$
.pipe(takeUntil(this.unsubscribe$))
.subscribe(this.resetTaskIndicator);
}

ngOnChanges(changes: SimpleChanges): void {
Expand All @@ -56,23 +70,28 @@ export class ActivityComponent implements OnInit, OnChanges {

const currentValue = changes.activity.currentValue;
if (currentValue.tasks?.length > 0) {
this.activityService.nonTeamActivity(changes.activity.currentValue?.tasks).then((nonTeamActivity) => {
this.isForTeamOnly = !nonTeamActivity;
this.cannotAccessTeamActivity.emit(this.isForTeamOnly);
});
this.activityService
.nonTeamActivity(changes.activity.currentValue?.tasks)
.then((nonTeamActivity) => {
this.isForTeamOnly = !nonTeamActivity;
this.cannotAccessTeamActivity.emit(this.isForTeamOnly);
});

const unlockedTasks = this.unlockIndicatorService.getTasksByActivity(this.activity);
this.resetTaskIndicator(unlockedTasks);
if (unlockedTasks.length === 0) {
const clearedActivities = this.unlockIndicatorService.clearActivity(this.activity.id);
clearedActivities.forEach((activity) => {
this.notificationsService.markTodoItemAsDone(activity).subscribe();
this.notificationsService
.markTodoItemAsDone(activity)
.pipe(takeUntil(this.unsubscribe$))
.subscribe();
});
}
}
}
}


/**
* Task icon type
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ export class AssessmentComponent implements OnInit, OnChanges, OnDestroy {
}),
).subscribe(
(data: {
autoSave: boolean;
autoSave: boolean; // true: this request is for autosave; false: request is for submission (manual submission);
goBack: boolean;
questionSave?: {
submissionId: number;
Expand Down Expand Up @@ -297,7 +297,6 @@ export class AssessmentComponent implements OnInit, OnChanges, OnDestroy {
this.doAssessment = true;
if (this.submission) {
this.savingMessage$.next($localize `Last saved ${this.utils.timeFormatter(this.submission.modified)}`);
this.btnDisabled$.next(false);
}
return;
}
Expand Down Expand Up @@ -637,6 +636,10 @@ export class AssessmentComponent implements OnInit, OnChanges, OnDestroy {
return this.utils.isColor('red', this.storage.getUser().colors?.primary);
}

/**
* Resubmit the assessment submission
* (mostly for regenerate AI feedback)
*/
resubmit(): Subscription {
if (!this.assessment?.id || !this.submission?.id || !this.activityId) {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ <h3 class="for-accessibility" [id]="'multi-team-member-selector-question-' + que
</ion-item>
</ng-container>
</ion-list>

<ng-container *ngIf="review?.comment">
<ion-item class="feedback-title ion-no-padding" lines="none">
<ion-icon class="ion-padding-horizontal" name="eye" size="small"></ion-icon>
Expand Down Expand Up @@ -57,13 +57,13 @@ <h3 class="for-accessibility" [id]="'multi-team-member-selector-question-' + que
[ngClass]="{'item-bottom-border': i !== question.teamMembers.length - 1 }">
<ion-label class="white-space-normal body-2 black">
{{ teamMember.userName }}
<p *ngIf="submission.answer.includes(teamMember.key)">
<p *ngIf="submission?.answer?.includes(teamMember.key)">
<ion-chip class="label orange" i18n>Learner's answer</ion-chip>
</p>
</ion-label>
<ion-checkbox color="success"
[attr.aria-label]="teamMember.userName"
[checked]="review.answer ? review.answer.includes(teamMember.key) : false"
[checked]="review?.answer?.includes(teamMember.key)"
[value]="teamMember.key"
slot="start"
(ionChange)="onChange(teamMember.key, 'answer')"
Expand Down
94 changes: 74 additions & 20 deletions projects/v3/src/app/pages/activity-desktop/activity-desktop.page.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { UnlockIndicatorService } from './../../services/unlock-indicator.service';
import { DOCUMENT } from '@angular/common';
import { Component, Inject, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
Expand All @@ -8,7 +9,7 @@ import { BrowserStorageService } from '@v3/app/services/storage.service';
import { Topic, TopicService } from '@v3/app/services/topic.service';
import { UtilsService } from '@v3/app/services/utils.service';
import { BehaviorSubject, Subscription } from 'rxjs';
import { delay, filter, tap } from 'rxjs/operators';
import { delay, filter, tap, distinctUntilChanged } from 'rxjs/operators';

const SAVE_PROGRESS_TIMEOUT = 10000;

Expand Down Expand Up @@ -45,7 +46,8 @@ export class ActivityDesktopPage {
private notificationsService: NotificationsService,
private storageService: BrowserStorageService,
private utils: UtilsService,
@Inject(DOCUMENT) private readonly document: Document
private unlockIndicatorService: UnlockIndicatorService,
@Inject(DOCUMENT) private readonly document: Document,
) { }

ionViewWillEnter() {
Expand All @@ -60,10 +62,14 @@ export class ActivityDesktopPage {
this.activityService.currentTask$.subscribe(res => this.currentTask = res)
);
this.subscriptions.push(
this.assessmentService.assessment$.subscribe(res => this.assessment = res)
this.assessmentService.assessment$
.pipe(distinctUntilChanged())
.subscribe(res => this.assessment = res)
);
this.subscriptions.push(
this.assessmentService.submission$.subscribe(res => this.submission = res)
this.assessmentService.submission$
.pipe(distinctUntilChanged())
.subscribe(res => this.submission = res)
);
this.subscriptions.push(
this.assessmentService.review$.subscribe(res => this.review = res)
Expand Down Expand Up @@ -117,6 +123,7 @@ export class ActivityDesktopPage {
});
}));

// refresh when review is available (AI review, peer review, etc.)
this.subscriptions.push(
this.utils.getEvent('notification').subscribe(event => {
const review = event?.meta?.AssessmentReview;
Expand All @@ -127,6 +134,17 @@ export class ActivityDesktopPage {
}
})
);

// check new unlock indicator to refresh
this.subscriptions.push(
this.unlockIndicatorService.unlockedTasks$.subscribe(unlockedTasks => {
if (this.activity) {
if (unlockedTasks.some(task => task.activityId === this.activity.id)) {
this.activityService.getActivity(this.activity.id);
}
}
})
);
}

ionViewWillLeave() {
Expand Down Expand Up @@ -191,31 +209,67 @@ export class ActivityDesktopPage {
this.btnDisabled$.next(true);
this.savingText$.next('Saving...');
try {
const saved = await this.assessmentService.submitAssessment(
event.submissionId,
event.assessmentId,
event.contextId,
event.answers
).toPromise();
// handle unexpected submission: do final status check before saving
let hasSubmssion = false;
const { submission } = await this.assessmentService
.fetchAssessment(
event.assessmentId,
"assessment",
this.activity.id,
event.contextId,
event.submissionId
)
.toPromise();

// http 200 but error
if (saved?.data?.submitAssessment?.success !== true || this.utils.isEmpty(saved)) {
throw new Error("Error submitting assessment");
}
if (submission?.status === 'in progress') {
const saved = await this.assessmentService
.submitAssessment(
event.submissionId,
event.assessmentId,
event.contextId,
event.answers
)
.toPromise();

if (this.assessment.pulseCheck === true && event.autoSave === false) {
await this.assessmentService.pullFastFeedback();
// http 200 but error
if (
saved?.data?.submitAssessment?.success !== true ||
this.utils.isEmpty(saved)
) {
throw new Error("Error submitting assessment");
}

if (this.assessment.pulseCheck === true && event.autoSave === false) {
await this.assessmentService.pullFastFeedback();
}
} else {
hasSubmssion = true;
}

this.savingText$.next($localize `Last saved ${this.utils.getFormatedCurrentTime()}`);
this.savingText$.next(
$localize`Last saved ${this.utils.getFormatedCurrentTime()}`
);

if (!event.autoSave) {
this.notificationsService.assessmentSubmittedToast();
if (hasSubmssion === true) {
this.notificationsService.assessmentSubmittedToast({ isDuplicated: true });
} else {
this.notificationsService.assessmentSubmittedToast();
}

await this.assessmentService.fetchAssessment(
event.assessmentId,
'assessment',
this.activity.id,
event.contextId,
event.submissionId
).toPromise();

// get the latest activity tasks
this.activityService.getActivity(this.activity.id, false, task, () => {
return this.activityService.getActivity(this.activity.id, false, task, () => {
this.loading = false;
this.btnDisabled$.next(false);
});
return this.assessmentService.getAssessment(event.assessmentId, 'assessment', this.activity.id, event.contextId, event.submissionId);
} else {
setTimeout(() => {
this.btnDisabled$.next(false);
Expand Down
Loading