MOBILE-3757 course: Update cached data after commpletion changed
parent
61a908216d
commit
ac8cc189f7
|
@ -1447,6 +1447,17 @@
|
|||
"core.course.askadmintosupport": "local_moodlemobileapp",
|
||||
"core.course.availablespace": "local_moodlemobileapp",
|
||||
"core.course.cannotdeletewhiledownloading": "local_moodlemobileapp",
|
||||
"core.course.completion_automatic:done": "course",
|
||||
"core.course.completion_automatic:failed": "course",
|
||||
"core.course.completion_automatic:todo": "course",
|
||||
"core.course.completion_manual:aria:done": "course",
|
||||
"core.course.completion_manual:aria:markdone": "course",
|
||||
"core.course.completion_manual:markdone": "course",
|
||||
"core.course.completion_setby:auto:done": "course",
|
||||
"core.course.completion_setby:auto:todo": "course",
|
||||
"core.course.completion_setby:manual:done": "course",
|
||||
"core.course.completion_setby:manual:markdone": "course",
|
||||
"core.course.completionrequirements": "course",
|
||||
"core.course.confirmdeletemodulefiles": "local_moodlemobileapp",
|
||||
"core.course.confirmdeletestoreddata": "local_moodlemobileapp",
|
||||
"core.course.confirmdownload": "local_moodlemobileapp",
|
||||
|
|
|
@ -79,6 +79,7 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
|
|||
protected currentStatus?: string; // The current status of the module. Only if setStatusListener is called.
|
||||
protected completionObserver?: CoreEventObserver;
|
||||
protected logger: CoreLogger;
|
||||
protected debouncedUpdateModule?: () => void; // Update the module after a certain time.
|
||||
|
||||
constructor(
|
||||
@Optional() @Inject('') loggerName: string = 'CoreCourseModuleMainResourceComponent',
|
||||
|
@ -103,9 +104,13 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
|
|||
if (data && data.cmId == this.module.id) {
|
||||
await CoreCourse.invalidateModule(this.module.id);
|
||||
|
||||
this.module = await CoreCourse.getModule(this.module.id, this.courseId);
|
||||
this.fetchModule();
|
||||
}
|
||||
});
|
||||
|
||||
this.debouncedUpdateModule = CoreUtils.debounce(() => {
|
||||
this.fetchModule();
|
||||
}, 10000);
|
||||
}
|
||||
|
||||
this.blog = await AddonBlog.isPluginEnabled();
|
||||
|
@ -160,7 +165,7 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
|
|||
]));
|
||||
|
||||
if (this.showCompletion) {
|
||||
this.module = await CoreCourse.getModule(this.module.id, this.courseId);
|
||||
this.fetchModule();
|
||||
}
|
||||
|
||||
await this.loadContent(true);
|
||||
|
@ -403,8 +408,23 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
|
|||
* @return Promise resolved when done.
|
||||
*/
|
||||
async onCompletionChange(): Promise<void> {
|
||||
// Nothing to do.
|
||||
return;
|
||||
// Update the module data after a while.
|
||||
this.debouncedUpdateModule?.();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch module.
|
||||
*
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected async fetchModule(): Promise<void> {
|
||||
const module = await CoreCourse.getModule(this.module.id, this.courseId);
|
||||
|
||||
CoreCourseHelper.calculateModuleCompletionData(module, this.courseId);
|
||||
|
||||
await CoreCourseHelper.loadModuleOfflineCompletion(this.courseId, module);
|
||||
|
||||
this.module = module;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -16,6 +16,7 @@ import { Component, Input } from '@angular/core';
|
|||
|
||||
import { CoreCourseModuleCompletionBaseComponent } from '@features/course/classes/module-completion';
|
||||
import { CoreCourseModuleWSRuleDetails, CoreCourseProvider } from '@features/course/services/course';
|
||||
import { CoreUser } from '@features/user/services/user';
|
||||
import { Translate } from '@singletons';
|
||||
|
||||
/**
|
||||
|
@ -43,13 +44,13 @@ export class CoreCourseModuleCompletionComponent extends CoreCourseModuleComplet
|
|||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected calculateData(): void {
|
||||
protected async calculateData(): Promise<void> {
|
||||
if (!this.completion?.details) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Format rules.
|
||||
this.details = this.completion.details.map((rule: CompletionRule) => {
|
||||
this.details = await Promise.all(this.completion.details.map(async (rule: CompletionRule) => {
|
||||
rule.statuscomplete = rule.rulevalue.status == CoreCourseProvider.COMPLETION_COMPLETE ||
|
||||
rule.rulevalue.status == CoreCourseProvider.COMPLETION_COMPLETE_PASS;
|
||||
rule.statuscompletefail = rule.rulevalue.status == CoreCourseProvider.COMPLETION_COMPLETE_FAIL;
|
||||
|
@ -57,10 +58,12 @@ export class CoreCourseModuleCompletionComponent extends CoreCourseModuleComplet
|
|||
rule.accessibleDescription = null;
|
||||
|
||||
if (this.completion!.overrideby) {
|
||||
const fullName = await CoreUser.getUserFullNameWithDefault(this.completion!.overrideby, this.completion!.courseId);
|
||||
|
||||
const setByData = {
|
||||
$a: {
|
||||
condition: rule.rulevalue.description,
|
||||
setby: this.completion!.overrideby,
|
||||
setby: fullName,
|
||||
},
|
||||
};
|
||||
const overrideStatus = rule.statuscomplete ? 'done' : 'todo';
|
||||
|
@ -69,7 +72,7 @@ export class CoreCourseModuleCompletionComponent extends CoreCourseModuleComplet
|
|||
}
|
||||
|
||||
return rule;
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
import { Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, SimpleChange } from '@angular/core';
|
||||
|
||||
import { CoreCourseHelper, CoreCourseModuleCompletionData } from '@features/course/services/course-helper';
|
||||
import { CoreUser } from '@features/user/services/user';
|
||||
import { Translate } from '@singletons';
|
||||
import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
||||
|
||||
|
@ -41,10 +42,13 @@ export class CoreCourseModuleManualCompletionComponent implements OnInit, OnChan
|
|||
*/
|
||||
ngOnInit(): void {
|
||||
this.manualChangedObserver = CoreEvents.on(CoreEvents.MANUAL_COMPLETION_CHANGED, (data) => {
|
||||
if (this.completion && this.completion.cmid == data.completion.cmid) {
|
||||
this.completion = data.completion;
|
||||
this.calculateData();
|
||||
if (!this.completion || this.completion.cmid != data.completion.cmid) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.completion = data.completion;
|
||||
this.calculateData();
|
||||
this.completionChanged.emit(this.completion);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -60,17 +64,19 @@ export class CoreCourseModuleManualCompletionComponent implements OnInit, OnChan
|
|||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected calculateData(): void {
|
||||
if (!this.completion?.isautomatic) {
|
||||
protected async calculateData(): Promise<void> {
|
||||
if (!this.completion || this.completion.isautomatic) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set an accessible description for manual completions with overridden completion state.
|
||||
if (this.completion.overrideby) {
|
||||
const fullName = await CoreUser.getUserFullNameWithDefault(this.completion.overrideby, this.completion.courseId);
|
||||
|
||||
const setByData = {
|
||||
$a: {
|
||||
activityname: this.moduleName,
|
||||
setby: this.completion.overrideby,
|
||||
setby: fullName,
|
||||
},
|
||||
};
|
||||
const setByLangKey = this.completion.state ? 'completion_setby:manual:done' : 'completion_setby:manual:markdone';
|
||||
|
@ -93,10 +99,7 @@ export class CoreCourseModuleManualCompletionComponent implements OnInit, OnChan
|
|||
|
||||
await CoreCourseHelper.changeManualCompletion(this.completion, event);
|
||||
|
||||
this.calculateData();
|
||||
|
||||
CoreEvents.trigger(CoreEvents.MANUAL_COMPLETION_CHANGED, { completion: this.completion });
|
||||
this.completionChanged.emit(this.completion);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -81,6 +81,8 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy {
|
|||
protected courseStatusObserver?: CoreEventObserver;
|
||||
protected syncObserver?: CoreEventObserver;
|
||||
protected isDestroyed = false;
|
||||
protected modulesHaveCompletion = false;
|
||||
protected debouncedUpdateCachedCompletion?: () => void; // Update the cached completion after a certain time.
|
||||
|
||||
/**
|
||||
* Component being initialized.
|
||||
|
@ -104,6 +106,21 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy {
|
|||
CoreCourseFormatDelegate.displayEnableDownload(this.course);
|
||||
this.downloadCourseEnabled = !CoreCourses.isDownloadCourseDisabledInSite();
|
||||
|
||||
this.debouncedUpdateCachedCompletion = CoreUtils.debounce(() => {
|
||||
if (this.modulesHaveCompletion) {
|
||||
CoreUtils.ignoreErrors(CoreCourse.getSections(this.course.id, false, true));
|
||||
} else {
|
||||
CoreUtils.ignoreErrors(CoreCourse.getActivitiesCompletionStatus(
|
||||
this.course.id,
|
||||
undefined,
|
||||
undefined,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
));
|
||||
}
|
||||
}, 30000);
|
||||
|
||||
this.initListeners();
|
||||
|
||||
await this.loadData(false, true);
|
||||
|
@ -254,6 +271,8 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy {
|
|||
|
||||
if (sectionWithModules && typeof sectionWithModules.modules[0].completion != 'undefined') {
|
||||
// The module already has completion (3.6 onwards). Load the offline completion.
|
||||
this.modulesHaveCompletion = true;
|
||||
|
||||
await CoreUtils.ignoreErrors(CoreCourseHelper.loadOfflineCompletion(this.course.id, sections));
|
||||
} else {
|
||||
const fetchedData = await CoreUtils.ignoreErrors(
|
||||
|
@ -356,6 +375,8 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy {
|
|||
// Invalidate the completion.
|
||||
await CoreUtils.ignoreErrors(CoreCourse.invalidateSections(this.course.id));
|
||||
|
||||
this.debouncedUpdateCachedCompletion?.();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -195,11 +195,8 @@ export class CoreCourseHelperProvider {
|
|||
forCoursePage,
|
||||
);
|
||||
|
||||
if (module.completiondata && module.completion && module.completion > 0) {
|
||||
module.completiondata.courseId = courseId;
|
||||
module.completiondata.courseName = courseName;
|
||||
module.completiondata.tracking = module.completion;
|
||||
module.completiondata.cmid = module.id;
|
||||
if (module.completiondata) {
|
||||
this.calculateModuleCompletionData(module, courseId, courseName);
|
||||
} else if (completionStatus && typeof completionStatus[module.id] != 'undefined') {
|
||||
// Should not happen on > 3.6. Check if activity has completions and if it's marked.
|
||||
const activityStatus = completionStatus[module.id];
|
||||
|
@ -224,6 +221,24 @@ export class CoreCourseHelperProvider {
|
|||
return { hasContent, sections: formattedSections };
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate completion data of a module.
|
||||
*
|
||||
* @param module Module.
|
||||
* @param courseId Course ID of the module.
|
||||
* @param courseName Course name.
|
||||
*/
|
||||
calculateModuleCompletionData(module: CoreCourseModule, courseId: number, courseName?: string): void {
|
||||
if (!module.completiondata || !module.completion) {
|
||||
return;
|
||||
}
|
||||
|
||||
module.completiondata.courseId = courseId;
|
||||
module.completiondata.courseName = courseName;
|
||||
module.completiondata.tracking = module.completion;
|
||||
module.completiondata.cmid = module.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the status of a section.
|
||||
*
|
||||
|
@ -1177,6 +1192,31 @@ export class CoreCourseHelperProvider {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load offline completion for a certain module.
|
||||
* This should be used in 3.6 sites or higher, where the course contents already include the completion.
|
||||
*
|
||||
* @param courseId The course to get the completion.
|
||||
* @param mmodule The module.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
async loadModuleOfflineCompletion(courseId: number, module: CoreCourseModule, siteId?: string): Promise<void> {
|
||||
if (!module.completiondata) {
|
||||
return;
|
||||
}
|
||||
|
||||
const offlineCompletions = await CoreCourseOffline.getCourseManualCompletions(courseId, siteId);
|
||||
|
||||
const offlineCompletion = offlineCompletions.find(completion => completion.cmid == module.id);
|
||||
|
||||
if (offlineCompletion && offlineCompletion.timecompleted >= module.completiondata.timecompleted * 1000) {
|
||||
// The module has offline completion. Load it.
|
||||
module.completiondata.state = offlineCompletion.completed;
|
||||
module.completiondata.offline = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefetch all the courses in the array.
|
||||
*
|
||||
|
|
|
@ -23,5 +23,6 @@
|
|||
"sendemail": "Email",
|
||||
"student": "Student",
|
||||
"teacher": "Non-editing teacher",
|
||||
"userwithid": "User with ID {{id}}",
|
||||
"webpage": "Web page"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ import { CoreUtils } from '@services/utils/utils';
|
|||
import { CoreUserOffline } from './user-offline';
|
||||
import { CoreLogger } from '@singletons/logger';
|
||||
import { CoreSite, CoreSiteWSPreSets } from '@classes/site';
|
||||
import { makeSingleton } from '@singletons';
|
||||
import { makeSingleton, Translate } from '@singletons';
|
||||
import { CoreEvents } from '@singletons/events';
|
||||
import { CoreStatusWithWarningsWSResponse, CoreWSExternalWarning } from '@services/ws';
|
||||
import { CoreError } from '@classes/errors/error';
|
||||
|
@ -305,6 +305,25 @@ export class CoreUserProvider {
|
|||
return site.getDb().getRecord(USERS_TABLE_NAME, { id: userId });
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a user fullname, using a default text if user not found.
|
||||
*
|
||||
* @param userId User ID.
|
||||
* @param courseId Course ID.
|
||||
* @param siteId Site ID.
|
||||
* @return Promise resolved with user name.
|
||||
*/
|
||||
async getUserFullNameWithDefault(userId: number, courseId?: number, siteId?: string): Promise<string> {
|
||||
try {
|
||||
const user = await CoreUser.getProfile(userId, courseId, true, siteId);
|
||||
|
||||
return user.fullname;
|
||||
|
||||
} catch {
|
||||
return Translate.instant('core.user.userwithid', { id: userId });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user profile from WS.
|
||||
*
|
||||
|
|
Loading…
Reference in New Issue