diff --git a/src/addons/mod/workshop/components/assessment-strategy/assessment-strategy.ts b/src/addons/mod/workshop/components/assessment-strategy/assessment-strategy.ts index 90afcbac2..892095ad3 100644 --- a/src/addons/mod/workshop/components/assessment-strategy/assessment-strategy.ts +++ b/src/addons/mod/workshop/components/assessment-strategy/assessment-strategy.ts @@ -38,6 +38,7 @@ import { } from '../../services/workshop'; import { AddonModWorkshopHelper, AddonModWorkshopSubmissionAssessmentWithFormData } from '../../services/workshop-helper'; import { AddonModWorkshopOffline } from '../../services/workshop-offline'; +import { ADDON_MOD_WORKSHOP_COMPONENT } from '@addons/mod/workshop/constants'; /** * Component that displays workshop assessment strategy form. @@ -75,7 +76,7 @@ export class AddonModWorkshopAssessmentStrategyComponent implements OnInit, OnDe feedbackControl = new FormControl(); overallFeedkback = false; overallFeedkbackRequired = false; - component = AddonModWorkshopProvider.COMPONENT; + component = ADDON_MOD_WORKSHOP_COMPONENT; componentId?: number; weights: number[] = []; weight?: number; @@ -128,7 +129,7 @@ export class AddonModWorkshopAssessmentStrategyComponent implements OnInit, OnDe // Check if rich text editor is enabled. if (this.edit) { // Block the workshop. - CoreSync.blockOperation(AddonModWorkshopProvider.COMPONENT, this.workshop.id); + CoreSync.blockOperation(ADDON_MOD_WORKSHOP_COMPONENT, this.workshop.id); } try { @@ -232,7 +233,7 @@ export class AddonModWorkshopAssessmentStrategyComponent implements OnInit, OnDe this.originalData.selectedValues = CoreUtils.clone(this.data.selectedValues); if (this.edit) { CoreFileSession.setFiles( - AddonModWorkshopProvider.COMPONENT, + ADDON_MOD_WORKSHOP_COMPONENT, this.workshop.id + '_' + this.assessmentId, this.data.assessment.feedbackattachmentfiles, ); @@ -265,7 +266,7 @@ export class AddonModWorkshopAssessmentStrategyComponent implements OnInit, OnDe // Compare feedback files. const files = CoreFileSession.getFiles( - AddonModWorkshopProvider.COMPONENT, + ADDON_MOD_WORKSHOP_COMPONENT, this.workshop.id + '_' + this.assessmentId, ) || []; if (CoreFileUploader.areFileListDifferent(files, this.originalData.files)) { @@ -290,7 +291,7 @@ export class AddonModWorkshopAssessmentStrategyComponent implements OnInit, OnDe } const files = CoreFileSession.getFiles( - AddonModWorkshopProvider.COMPONENT, + ADDON_MOD_WORKSHOP_COMPONENT, this.workshop.id + '_' + this.assessmentId, ) || []; diff --git a/src/addons/mod/workshop/components/index/index.ts b/src/addons/mod/workshop/components/index/index.ts index 8b7c54776..c8a255e7a 100644 --- a/src/addons/mod/workshop/components/index/index.ts +++ b/src/addons/mod/workshop/components/index/index.ts @@ -25,7 +25,6 @@ import { CoreDomUtils } from '@services/utils/dom'; import { CoreUtils } from '@services/utils/utils'; import { CoreEventObserver, CoreEvents } from '@singletons/events'; import { Subscription } from 'rxjs'; -import { AddonModWorkshopModuleHandlerService } from '../../services/handlers/module'; import { AddonModWorkshopProvider, AddonModWorkshopPhase, @@ -53,6 +52,7 @@ import { AddonModWorkshopSyncResult, } from '../../services/workshop-sync'; import { AddonModWorkshopPhaseInfoComponent } from '../phase/phase'; +import { ADDON_MOD_WORKSHOP_COMPONENT, ADDON_MOD_WORKSHOP_PAGE_NAME } from '@addons/mod/workshop/constants'; /** * Component that displays a workshop index page. @@ -65,7 +65,7 @@ export class AddonModWorkshopIndexComponent extends CoreCourseModuleMainActivity @Input() group = 0; - component = AddonModWorkshopProvider.COMPONENT; + component = ADDON_MOD_WORKSHOP_COMPONENT; pluginName = 'workshop'; workshop?: AddonModWorkshopData; @@ -379,7 +379,7 @@ export class AddonModWorkshopIndexComponent extends CoreCourseModuleMainActivity const submissionId = this.submission?.id || 0; CoreNavigator.navigateToSitePath( - AddonModWorkshopModuleHandlerService.PAGE_NAME + `/${this.courseId}/${this.module.id}/${submissionId}/edit`, + `${ADDON_MOD_WORKSHOP_PAGE_NAME}/${this.courseId}/${this.module.id}/${submissionId}/edit`, { params }, ); diff --git a/src/addons/mod/workshop/components/submission/submission.ts b/src/addons/mod/workshop/components/submission/submission.ts index 1e3d0c766..8d49a967c 100644 --- a/src/addons/mod/workshop/components/submission/submission.ts +++ b/src/addons/mod/workshop/components/submission/submission.ts @@ -19,9 +19,7 @@ import { CoreUser, CoreUserProfile } from '@features/user/services/user'; import { CoreNavigator } from '@services/navigator'; import { CoreSites } from '@services/sites'; import { AddonModWorkshopSubmissionPage } from '../../pages/submission/submission'; -import { AddonModWorkshopModuleHandlerService } from '../../services/handlers/module'; import { - AddonModWorkshopProvider, AddonModWorkshopPhase, AddonModWorkshopData, AddonModWorkshopGetWorkshopAccessInformationWSResponse, @@ -32,6 +30,7 @@ import { AddonModWorkshopSubmissionDataWithOfflineData, } from '../../services/workshop-helper'; import { AddonModWorkshopOffline } from '../../services/workshop-offline'; +import { ADDON_MOD_WORKSHOP_COMPONENT, ADDON_MOD_WORKSHOP_PAGE_NAME } from '@addons/mod/workshop/constants'; /** * Component that displays workshop submission. @@ -51,7 +50,7 @@ export class AddonModWorkshopSubmissionComponent implements OnInit { @Input() assessment?: AddonModWorkshopSubmissionAssessmentWithFormData; @Input() summary = false; - component = AddonModWorkshopProvider.COMPONENT; + component = ADDON_MOD_WORKSHOP_COMPONENT; componentId?: number; userId: number; loaded = false; @@ -128,7 +127,7 @@ export class AddonModWorkshopSubmissionComponent implements OnInit { }; CoreNavigator.navigateToSitePath( - AddonModWorkshopModuleHandlerService.PAGE_NAME + `/${this.courseId}/${this.module.id}/${this.submission.id}`, + `${ADDON_MOD_WORKSHOP_PAGE_NAME}/${this.courseId}/${this.module.id}/${this.submission.id}`, { params }, ); diff --git a/src/addons/mod/workshop/constants.ts b/src/addons/mod/workshop/constants.ts new file mode 100644 index 000000000..a1de91991 --- /dev/null +++ b/src/addons/mod/workshop/constants.ts @@ -0,0 +1,41 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +export const ADDON_MOD_WORKSHOP_COMPONENT = 'mmaModWorkshop'; + +// Routing. +export const ADDON_MOD_WORKSHOP_PAGE_NAME = 'mod_workshop'; + +// Handlers. +export const ADDON_MOD_WORKSHOP_PREFETCH_NAME = 'AddonModWorkshop'; +export const ADDON_MOD_WORKSHOP_PREFETCH_MODNAME = 'workshop'; +export const ADDON_MOD_WORKSHOP_PREFETCH_COMPONENT = ADDON_MOD_WORKSHOP_COMPONENT; +export const ADDON_MOD_WORKSHOP_PREFETCH_UPDATE_NAMES = new RegExp( + [ + '^configuration$', + '^.*files$', + '^completion', + '^gradeitems$', + '^outcomes$', + '^submissions$', + '^assessments$' + + '^assessmentgrades$', + '^usersubmissions$', + '^userassessments$', + '^userassessmentgrades$', + '^userassessmentgrades$', + ].join('|'), +); + +export const ADDON_MOD_WORKSHOP_SYNC_CRON_NAME = 'AddonModWorkshopSyncCronHandler'; diff --git a/src/addons/mod/workshop/pages/assessment/assessment.ts b/src/addons/mod/workshop/pages/assessment/assessment.ts index 973a16a03..82592509b 100644 --- a/src/addons/mod/workshop/pages/assessment/assessment.ts +++ b/src/addons/mod/workshop/pages/assessment/assessment.ts @@ -41,6 +41,7 @@ import { AddonModWorkshopOffline } from '../../services/workshop-offline'; import { AddonModWorkshopSyncProvider } from '../../services/workshop-sync'; import { CoreTime } from '@singletons/time'; import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; +import { ADDON_MOD_WORKSHOP_COMPONENT } from '@addons/mod/workshop/constants'; /** * Page that displays a workshop assessment. @@ -198,7 +199,7 @@ export class AddonModWorkshopAssessmentPage implements OnInit, OnDestroy, CanLea if (this.assessmentId && (this.access.canallocate || this.access.canoverridegrades)) { if (!this.isDestroyed) { // Block the workshop. - CoreSync.blockOperation(AddonModWorkshopProvider.COMPONENT, this.workshopId); + CoreSync.blockOperation(ADDON_MOD_WORKSHOP_COMPONENT, this.workshopId); } this.evaluating = true; @@ -413,7 +414,7 @@ export class AddonModWorkshopAssessmentPage implements OnInit, OnDestroy, CanLea this.syncObserver?.off(); // Restore original back functions. - CoreSync.unblockOperation(AddonModWorkshopProvider.COMPONENT, this.workshopId); + CoreSync.unblockOperation(ADDON_MOD_WORKSHOP_COMPONENT, this.workshopId); } } diff --git a/src/addons/mod/workshop/pages/edit-submission/edit-submission.ts b/src/addons/mod/workshop/pages/edit-submission/edit-submission.ts index 4c7ea2d08..5d7b73734 100644 --- a/src/addons/mod/workshop/pages/edit-submission/edit-submission.ts +++ b/src/addons/mod/workshop/pages/edit-submission/edit-submission.ts @@ -41,6 +41,7 @@ import { import { AddonModWorkshopHelper, AddonModWorkshopSubmissionDataWithOfflineData } from '../../services/workshop-helper'; import { AddonModWorkshopOffline } from '../../services/workshop-offline'; import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; +import { ADDON_MOD_WORKSHOP_COMPONENT } from '@addons/mod/workshop/constants'; /** * Page that displays the workshop edit submission. @@ -59,7 +60,7 @@ export class AddonModWorkshopEditSubmissionPage implements OnInit, OnDestroy, Ca submission?: AddonModWorkshopSubmissionDataWithOfflineData; loaded = false; - component = AddonModWorkshopProvider.COMPONENT; + component = ADDON_MOD_WORKSHOP_COMPONENT; componentId!: number; editForm: FormGroup; // The form group. editorExtraParams: Record = {}; // Extra params to identify the draft. diff --git a/src/addons/mod/workshop/pages/submission/submission.ts b/src/addons/mod/workshop/pages/submission/submission.ts index faf95933b..bfb7c87b9 100644 --- a/src/addons/mod/workshop/pages/submission/submission.ts +++ b/src/addons/mod/workshop/pages/submission/submission.ts @@ -49,6 +49,7 @@ import { AddonModWorkshopOffline } from '../../services/workshop-offline'; import { AddonModWorkshopSyncProvider, AddonModWorkshopAutoSyncData } from '../../services/workshop-sync'; import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; import { CoreTime } from '@singletons/time'; +import { ADDON_MOD_WORKSHOP_COMPONENT } from '@addons/mod/workshop/constants'; /** * Page that displays a workshop submission. @@ -99,7 +100,7 @@ export class AddonModWorkshopSubmissionPage implements OnInit, OnDestroy, CanLea }; protected hasOffline = false; - protected component = AddonModWorkshopProvider.COMPONENT; + protected component = ADDON_MOD_WORKSHOP_COMPONENT; protected forceLeave = false; protected obsAssessmentSaved: CoreEventObserver; protected syncObserver: CoreEventObserver; diff --git a/src/addons/mod/workshop/services/handlers/index-link.ts b/src/addons/mod/workshop/services/handlers/index-link.ts index 3ad62aba9..98469d0cb 100644 --- a/src/addons/mod/workshop/services/handlers/index-link.ts +++ b/src/addons/mod/workshop/services/handlers/index-link.ts @@ -15,7 +15,8 @@ import { Injectable } from '@angular/core'; import { CoreContentLinksModuleIndexHandler } from '@features/contentlinks/classes/module-index-handler'; import { makeSingleton } from '@singletons'; -import { AddonModWorkshopProvider } from '../workshop'; +import { ADDON_MOD_WORKSHOP_COMPONENT } from '@addons/mod/workshop/constants'; + /** * Handler to treat links to workshop. */ @@ -25,7 +26,7 @@ export class AddonModWorkshopIndexLinkHandlerService extends CoreContentLinksMod name = 'AddonModWorkshopLinkHandler'; constructor() { - super(AddonModWorkshopProvider.COMPONENT, 'workshop', 'w'); + super(ADDON_MOD_WORKSHOP_COMPONENT, 'workshop', 'w'); } } diff --git a/src/addons/mod/workshop/services/handlers/module.ts b/src/addons/mod/workshop/services/handlers/module.ts index 869c347cb..4ec4d181c 100644 --- a/src/addons/mod/workshop/services/handlers/module.ts +++ b/src/addons/mod/workshop/services/handlers/module.ts @@ -13,11 +13,11 @@ // limitations under the License. import { CoreConstants, ModPurpose } from '@/core/constants'; +import { ADDON_MOD_WORKSHOP_PAGE_NAME } from '@addons/mod/workshop/constants'; import { Injectable, Type } from '@angular/core'; import { CoreModuleHandlerBase } from '@features/course/classes/module-base-handler'; import { CoreCourseModuleHandler } from '@features/course/services/module-delegate'; import { makeSingleton } from '@singletons'; -import { AddonModWorkshopIndexComponent } from '../../components/index'; /** * Handler to support workshop modules. @@ -25,11 +25,9 @@ import { AddonModWorkshopIndexComponent } from '../../components/index'; @Injectable({ providedIn: 'root' }) export class AddonModWorkshopModuleHandlerService extends CoreModuleHandlerBase implements CoreCourseModuleHandler { - static readonly PAGE_NAME = 'mod_workshop'; - name = 'AddonModWorkshop'; modName = 'workshop'; - protected pageName = AddonModWorkshopModuleHandlerService.PAGE_NAME; + protected pageName = ADDON_MOD_WORKSHOP_PAGE_NAME; supportedFeatures = { [CoreConstants.FEATURE_GROUPS]: true, @@ -47,6 +45,8 @@ export class AddonModWorkshopModuleHandlerService extends CoreModuleHandlerBase * @inheritdoc */ async getMainComponent(): Promise> { + const { AddonModWorkshopIndexComponent } = await import('@addons/mod/workshop/components/index'); + return AddonModWorkshopIndexComponent; } diff --git a/src/addons/mod/workshop/services/handlers/prefetch-lazy.ts b/src/addons/mod/workshop/services/handlers/prefetch-lazy.ts new file mode 100644 index 000000000..b48227563 --- /dev/null +++ b/src/addons/mod/workshop/services/handlers/prefetch-lazy.ts @@ -0,0 +1,405 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { AddonModDataSyncResult } from '@addons/mod/data/services/data-sync'; +import { Injectable } from '@angular/core'; +import { CoreCourse, CoreCourseAnyModuleData } from '@features/course/services/course'; +import { CoreCourses } from '@features/courses/services/courses'; +import { CoreUser } from '@features/user/services/user'; +import { CoreFilepool } from '@services/filepool'; +import { CoreGroup, CoreGroups } from '@services/groups'; +import { CoreSites, CoreSitesReadingStrategy, CoreSitesCommonWSOptions } from '@services/sites'; +import { CoreUtils } from '@services/utils/utils'; +import { CoreWSExternalFile, CoreWSFile } from '@services/ws'; +import { makeSingleton } from '@singletons'; +import { + AddonModWorkshop, + AddonModWorkshopPhase, + AddonModWorkshopGradesData, + AddonModWorkshopData, + AddonModWorkshopGetWorkshopAccessInformationWSResponse, +} from '../workshop'; +import { AddonModWorkshopHelper } from '../workshop-helper'; +import { AddonModWorkshopSync } from '../workshop-sync'; +import { AddonModWorkshopPrefetchHandlerService } from '@addons/mod/workshop/services/handlers/prefetch'; + +/** + * Handler to prefetch workshops. + */ +@Injectable({ providedIn: 'root' }) +export class AddonModWorkshopPrefetchHandlerLazyService extends AddonModWorkshopPrefetchHandlerService { + + /** + * @inheritdoc + */ + async getFiles(module: CoreCourseAnyModuleData, courseId: number): Promise { + const info = await this.getWorkshopInfoHelper(module, courseId, { omitFail: true }); + + return info.files; + } + + /** + * Helper function to get all workshop info just once. + * + * @param module Module to get the files. + * @param courseId Course ID the module belongs to. + * @param options Other options. + * @returns Promise resolved with the info fetched. + */ + protected async getWorkshopInfoHelper( + module: CoreCourseAnyModuleData, + courseId: number, + options: AddonModWorkshopGetInfoOptions = {}, + ): Promise<{ workshop?: AddonModWorkshopData; groups: CoreGroup[]; files: CoreWSFile[]}> { + let groups: CoreGroup[] = []; + let files: CoreWSFile[] = []; + let workshop: AddonModWorkshopData; + let access: AddonModWorkshopGetWorkshopAccessInformationWSResponse | undefined; + + const modOptions = { + cmId: module.id, + ...options, // Include all options. + }; + + const site = await CoreSites.getSite(options.siteId); + options.siteId = options.siteId ?? site.getId(); + const userId = site.getUserId(); + + try { + workshop = await AddonModWorkshop.getWorkshop(courseId, module.id, options); + } catch (error) { + if (options.omitFail) { + // Any error, return the info we have. + return { + groups: [], + files: [], + }; + } + + throw error; + } + + try { + files = this.getIntroFilesFromInstance(module, workshop); + files = files.concat(workshop.instructauthorsfiles || []).concat(workshop.instructreviewersfiles || []); + + access = await AddonModWorkshop.getWorkshopAccessInformation(workshop.id, modOptions); + if (access.canviewallsubmissions) { + const groupInfo = await CoreGroups.getActivityGroupInfo(module.id, false, undefined, options.siteId); + if (!groupInfo.groups || groupInfo.groups.length == 0) { + groupInfo.groups = [{ id: 0, name: '' }]; + } + groups = groupInfo.groups; + } + + const phases = await AddonModWorkshop.getUserPlanPhases(workshop.id, modOptions); + + // Get submission phase info. + const submissionPhase = phases[AddonModWorkshopPhase.PHASE_SUBMISSION]; + const canSubmit = AddonModWorkshopHelper.canSubmit(workshop, access, submissionPhase.tasks); + const canAssess = AddonModWorkshopHelper.canAssess(workshop, access); + + const promises: Promise[] = []; + + if (canSubmit) { + promises.push(AddonModWorkshopHelper.getUserSubmission(workshop.id, { + userId, + cmId: module.id, + }).then((submission) => { + if (submission) { + files = files.concat(submission.contentfiles || []).concat(submission.attachmentfiles || []); + } + + return; + })); + } + + if (access.canviewallsubmissions && workshop.phase >= AddonModWorkshopPhase.PHASE_SUBMISSION) { + promises.push(AddonModWorkshop.getSubmissions(workshop.id, modOptions).then(async (submissions) => { + + await Promise.all(submissions.map(async (submission) => { + files = files.concat(submission.contentfiles || []).concat(submission.attachmentfiles || []); + + const assessments = await AddonModWorkshop.getSubmissionAssessments(workshop.id, submission.id, { + cmId: module.id, + }); + + assessments.forEach((assessment) => { + files = files.concat(assessment.feedbackattachmentfiles) + .concat(assessment.feedbackcontentfiles); + }); + + if (workshop.phase >= AddonModWorkshopPhase.PHASE_ASSESSMENT && canAssess) { + await Promise.all(assessments.map((assessment) => + AddonModWorkshopHelper.getReviewerAssessmentById(workshop.id, assessment.id))); + } + })); + + return; + })); + } + + // Get assessment files. + if (workshop.phase >= AddonModWorkshopPhase.PHASE_ASSESSMENT && canAssess) { + promises.push(AddonModWorkshopHelper.getReviewerAssessments(workshop.id, modOptions).then((assessments) => { + assessments.forEach((assessment) => { + files = files.concat(assessment.feedbackattachmentfiles) + .concat(assessment.feedbackcontentfiles); + }); + + return; + })); + } + + await Promise.all(promises); + + return { + workshop, + groups, + files: files.filter((file) => file !== undefined), + }; + } catch (error) { + if (options.omitFail) { + // Any error, return the info we have. + return { + workshop, + groups, + files: files.filter((file) => file !== undefined), + }; + } + + throw error; + } + } + + /** + * @inheritdoc + */ + async invalidateContent(moduleId: number, courseId: number): Promise { + await AddonModWorkshop.invalidateContent(moduleId, courseId); + } + + /** + * Check if a module can be downloaded. If the function is not defined, we assume that all modules are downloadable. + * + * @param module Module. + * @param courseId Course ID the module belongs to. + * @returns Whether the module can be downloaded. The promise should never be rejected. + */ + async isDownloadable(module: CoreCourseAnyModuleData, courseId: number): Promise { + const workshop = await AddonModWorkshop.getWorkshop(courseId, module.id, { + readingStrategy: CoreSitesReadingStrategy.PREFER_CACHE, + }); + + const accessData = await AddonModWorkshop.getWorkshopAccessInformation(workshop.id, { cmId: module.id }); + + // Check if workshop is setup by phase. + return accessData.canswitchphase || workshop.phase > AddonModWorkshopPhase.PHASE_SETUP; + } + + /** + * @inheritdoc + */ + prefetch(module: CoreCourseAnyModuleData, courseId: number): Promise { + return this.prefetchPackage(module, courseId, (siteId) => this.prefetchWorkshop(module, courseId, siteId)); + } + + /** + * Retrieves all the grades reports for all the groups and then returns only unique grades. + * + * @param workshopId Workshop ID. + * @param groups Array of groups in the activity. + * @param cmId Module ID. + * @param siteId Site ID. If not defined, current site. + * @returns All unique entries. + */ + protected async getAllGradesReport( + workshopId: number, + groups: CoreGroup[], + cmId: number, + siteId: string, + ): Promise { + const promises: Promise[] = []; + + groups.forEach((group) => { + promises.push(AddonModWorkshop.fetchAllGradeReports(workshopId, { groupId: group.id, cmId, siteId })); + }); + + const grades = await Promise.all(promises); + const uniqueGrades: Record = {}; + + grades.forEach((groupGrades) => { + groupGrades.forEach((grade) => { + if (grade.submissionid) { + uniqueGrades[grade.submissionid] = grade; + } + }); + }); + + return CoreUtils.objectToArray(uniqueGrades); + } + + /** + * Prefetch a workshop. + * + * @param module The module object returned by WS. + * @param courseId Course ID the module belongs to. + * @param siteId Site ID. + * @returns Promise resolved when done. + */ + protected async prefetchWorkshop(module: CoreCourseAnyModuleData, courseId: number, siteId: string): Promise { + const userIds: number[] = []; + const commonOptions = { + readingStrategy: CoreSitesReadingStrategy.ONLY_NETWORK, + siteId, + }; + const modOptions = { + cmId: module.id, + ...commonOptions, // Include all common options. + }; + + const site = await CoreSites.getSite(siteId); + const currentUserId = site.getUserId(); + + // Prefetch the workshop data. + const info = await this.getWorkshopInfoHelper(module, courseId, commonOptions); + if (!info.workshop) { + // It would throw an exception so it would not happen. + return; + } + + const workshop = info.workshop; + const promises: Promise[] = []; + const assessmentIds: number[] = []; + + promises.push(CoreFilepool.addFilesToQueue(siteId, info.files, this.component, module.id)); + + promises.push(AddonModWorkshop.getWorkshopAccessInformation(workshop.id, modOptions).then(async (access) => { + const phases = await AddonModWorkshop.getUserPlanPhases(workshop.id, modOptions); + + // Get submission phase info. + const submissionPhase = phases[AddonModWorkshopPhase.PHASE_SUBMISSION]; + const canSubmit = AddonModWorkshopHelper.canSubmit(workshop, access, submissionPhase.tasks); + const canAssess = AddonModWorkshopHelper.canAssess(workshop, access); + const promises2: Promise[] = []; + + if (canSubmit) { + promises2.push(AddonModWorkshop.getSubmissions(workshop.id, modOptions)); + // Add userId to the profiles to prefetch. + userIds.push(currentUserId); + } + + let reportPromise: Promise = Promise.resolve(); + if (access.canviewallsubmissions && workshop.phase >= AddonModWorkshopPhase.PHASE_SUBMISSION) { + // eslint-disable-next-line promise/no-nesting + reportPromise = this.getAllGradesReport(workshop.id, info.groups, module.id, siteId).then((grades) => { + grades.forEach((grade) => { + userIds.push(grade.userid); + grade.submissiongradeoverby && userIds.push(grade.submissiongradeoverby); + + grade.reviewedby && grade.reviewedby.forEach((assessment) => { + userIds.push(assessment.userid); + assessmentIds[assessment.assessmentid] = assessment.assessmentid; + }); + + grade.reviewerof && grade.reviewerof.forEach((assessment) => { + userIds.push(assessment.userid); + assessmentIds[assessment.assessmentid] = assessment.assessmentid; + }); + }); + + return; + }); + } + + if (workshop.phase >= AddonModWorkshopPhase.PHASE_ASSESSMENT && canAssess) { + // Wait the report promise to finish to override assessments array if needed. + reportPromise = reportPromise.finally(async () => { + const revAssessments = await AddonModWorkshopHelper.getReviewerAssessments(workshop.id, { + userId: currentUserId, + cmId: module.id, + siteId, + }); + + let files: CoreWSExternalFile[] = []; // Files in each submission. + + revAssessments.forEach((assessment) => { + if (assessment.submission?.authorid == currentUserId) { + promises.push(AddonModWorkshop.getAssessment( + workshop.id, + assessment.id, + modOptions, + )); + } + userIds.push(assessment.reviewerid); + userIds.push(assessment.gradinggradeoverby); + assessmentIds[assessment.id] = assessment.id; + + files = files.concat(assessment.submission?.attachmentfiles || []) + .concat(assessment.submission?.contentfiles || []); + }); + + await CoreFilepool.addFilesToQueue(siteId, files, this.component, module.id); + }); + } + + reportPromise = reportPromise.finally(() => { + if (assessmentIds.length > 0) { + return Promise.all(assessmentIds.map((assessmentId) => + AddonModWorkshop.getAssessmentForm(workshop.id, assessmentId, modOptions))); + } + }); + promises2.push(reportPromise); + + if (workshop.phase == AddonModWorkshopPhase.PHASE_CLOSED) { + promises2.push(AddonModWorkshop.getGrades(workshop.id, modOptions)); + if (access.canviewpublishedsubmissions) { + promises2.push(AddonModWorkshop.getSubmissions(workshop.id, modOptions)); + } + } + + await Promise.all(promises2); + + return; + })); + + // Add Basic Info to manage links. + promises.push(CoreCourse.getModuleBasicInfoByInstance(workshop.id, 'workshop', { siteId })); + promises.push(CoreCourse.getModuleBasicGradeInfo(module.id, siteId)); + + // Get course data, needed to determine upload max size if it's configured to be course limit. + promises.push(CoreUtils.ignoreErrors(CoreCourses.getCourseByField('id', courseId, siteId))); + + await Promise.all(promises); + + // Prefetch user profiles. + await CoreUser.prefetchProfiles(userIds, courseId, siteId); + } + + /** + * @inheritdoc + */ + async sync(module: CoreCourseAnyModuleData, courseId: number, siteId?: string): Promise { + return AddonModWorkshopSync.syncWorkshop(module.instance, siteId); + } + +} +export const AddonModWorkshopPrefetchHandler = makeSingleton(AddonModWorkshopPrefetchHandlerLazyService); + +/** + * Options to pass to getWorkshopInfoHelper. + */ +export type AddonModWorkshopGetInfoOptions = CoreSitesCommonWSOptions & { + omitFail?: boolean; // True to always return even if fails. +}; diff --git a/src/addons/mod/workshop/services/handlers/prefetch.ts b/src/addons/mod/workshop/services/handlers/prefetch.ts index 2da580e75..c4c8ef95a 100644 --- a/src/addons/mod/workshop/services/handlers/prefetch.ts +++ b/src/addons/mod/workshop/services/handlers/prefetch.ts @@ -12,401 +12,38 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { AddonModDataSyncResult } from '@addons/mod/data/services/data-sync'; -import { Injectable } from '@angular/core'; -import { CoreCourseActivityPrefetchHandlerBase } from '@features/course/classes/activity-prefetch-handler'; -import { CoreCourse, CoreCourseAnyModuleData } from '@features/course/services/course'; -import { CoreCourses } from '@features/courses/services/courses'; -import { CoreUser } from '@features/user/services/user'; -import { CoreFilepool } from '@services/filepool'; -import { CoreGroup, CoreGroups } from '@services/groups'; -import { CoreSites, CoreSitesReadingStrategy, CoreSitesCommonWSOptions } from '@services/sites'; -import { CoreUtils } from '@services/utils/utils'; -import { CoreWSExternalFile, CoreWSFile } from '@services/ws'; -import { makeSingleton } from '@singletons'; +import { asyncInstance } from '@/core/utils/async-instance'; import { - AddonModWorkshopProvider, - AddonModWorkshop, - AddonModWorkshopPhase, - AddonModWorkshopGradesData, - AddonModWorkshopData, - AddonModWorkshopGetWorkshopAccessInformationWSResponse, -} from '../workshop'; -import { AddonModWorkshopHelper } from '../workshop-helper'; -import { AddonModWorkshopSync } from '../workshop-sync'; + ADDON_MOD_WORKSHOP_PREFETCH_COMPONENT, + ADDON_MOD_WORKSHOP_PREFETCH_MODNAME, + ADDON_MOD_WORKSHOP_PREFETCH_NAME, + ADDON_MOD_WORKSHOP_PREFETCH_UPDATE_NAMES, +} from '@addons/mod/workshop/constants'; +import { CoreCourseActivityPrefetchHandlerBase } from '@features/course/classes/activity-prefetch-handler'; +import { CoreCourseModulePrefetchHandler } from '@features/course/services/module-prefetch-delegate'; -/** - * Handler to prefetch workshops. - */ -@Injectable({ providedIn: 'root' }) export class AddonModWorkshopPrefetchHandlerService extends CoreCourseActivityPrefetchHandlerBase { - name = 'AddonModWorkshop'; - modName = 'workshop'; - component = AddonModWorkshopProvider.COMPONENT; - updatesNames = new RegExp('^configuration$|^.*files$|^completion|^gradeitems$|^outcomes$|^submissions$|^assessments$' + - '|^assessmentgrades$|^usersubmissions$|^userassessments$|^userassessmentgrades$|^userassessmentgrades$'); - - /** - * @inheritdoc - */ - async getFiles(module: CoreCourseAnyModuleData, courseId: number): Promise { - const info = await this.getWorkshopInfoHelper(module, courseId, { omitFail: true }); - - return info.files; - } - - /** - * Helper function to get all workshop info just once. - * - * @param module Module to get the files. - * @param courseId Course ID the module belongs to. - * @param options Other options. - * @returns Promise resolved with the info fetched. - */ - protected async getWorkshopInfoHelper( - module: CoreCourseAnyModuleData, - courseId: number, - options: AddonModWorkshopGetInfoOptions = {}, - ): Promise<{ workshop?: AddonModWorkshopData; groups: CoreGroup[]; files: CoreWSFile[]}> { - let groups: CoreGroup[] = []; - let files: CoreWSFile[] = []; - let workshop: AddonModWorkshopData; - let access: AddonModWorkshopGetWorkshopAccessInformationWSResponse | undefined; - - const modOptions = { - cmId: module.id, - ...options, // Include all options. - }; - - const site = await CoreSites.getSite(options.siteId); - options.siteId = options.siteId ?? site.getId(); - const userId = site.getUserId(); - - try { - workshop = await AddonModWorkshop.getWorkshop(courseId, module.id, options); - } catch (error) { - if (options.omitFail) { - // Any error, return the info we have. - return { - groups: [], - files: [], - }; - } - - throw error; - } - - try { - files = this.getIntroFilesFromInstance(module, workshop); - files = files.concat(workshop.instructauthorsfiles || []).concat(workshop.instructreviewersfiles || []); - - access = await AddonModWorkshop.getWorkshopAccessInformation(workshop.id, modOptions); - if (access.canviewallsubmissions) { - const groupInfo = await CoreGroups.getActivityGroupInfo(module.id, false, undefined, options.siteId); - if (!groupInfo.groups || groupInfo.groups.length == 0) { - groupInfo.groups = [{ id: 0, name: '' }]; - } - groups = groupInfo.groups; - } - - const phases = await AddonModWorkshop.getUserPlanPhases(workshop.id, modOptions); - - // Get submission phase info. - const submissionPhase = phases[AddonModWorkshopPhase.PHASE_SUBMISSION]; - const canSubmit = AddonModWorkshopHelper.canSubmit(workshop, access, submissionPhase.tasks); - const canAssess = AddonModWorkshopHelper.canAssess(workshop, access); - - const promises: Promise[] = []; - - if (canSubmit) { - promises.push(AddonModWorkshopHelper.getUserSubmission(workshop.id, { - userId, - cmId: module.id, - }).then((submission) => { - if (submission) { - files = files.concat(submission.contentfiles || []).concat(submission.attachmentfiles || []); - } - - return; - })); - } - - if (access.canviewallsubmissions && workshop.phase >= AddonModWorkshopPhase.PHASE_SUBMISSION) { - promises.push(AddonModWorkshop.getSubmissions(workshop.id, modOptions).then(async (submissions) => { - - await Promise.all(submissions.map(async (submission) => { - files = files.concat(submission.contentfiles || []).concat(submission.attachmentfiles || []); - - const assessments = await AddonModWorkshop.getSubmissionAssessments(workshop.id, submission.id, { - cmId: module.id, - }); - - assessments.forEach((assessment) => { - files = files.concat(assessment.feedbackattachmentfiles) - .concat(assessment.feedbackcontentfiles); - }); - - if (workshop.phase >= AddonModWorkshopPhase.PHASE_ASSESSMENT && canAssess) { - await Promise.all(assessments.map((assessment) => - AddonModWorkshopHelper.getReviewerAssessmentById(workshop.id, assessment.id))); - } - })); - - return; - })); - } - - // Get assessment files. - if (workshop.phase >= AddonModWorkshopPhase.PHASE_ASSESSMENT && canAssess) { - promises.push(AddonModWorkshopHelper.getReviewerAssessments(workshop.id, modOptions).then((assessments) => { - assessments.forEach((assessment) => { - files = files.concat(assessment.feedbackattachmentfiles) - .concat(assessment.feedbackcontentfiles); - }); - - return; - })); - } - - await Promise.all(promises); - - return { - workshop, - groups, - files: files.filter((file) => file !== undefined), - }; - } catch (error) { - if (options.omitFail) { - // Any error, return the info we have. - return { - workshop, - groups, - files: files.filter((file) => file !== undefined), - }; - } - - throw error; - } - } - - /** - * @inheritdoc - */ - async invalidateContent(moduleId: number, courseId: number): Promise { - await AddonModWorkshop.invalidateContent(moduleId, courseId); - } - - /** - * Check if a module can be downloaded. If the function is not defined, we assume that all modules are downloadable. - * - * @param module Module. - * @param courseId Course ID the module belongs to. - * @returns Whether the module can be downloaded. The promise should never be rejected. - */ - async isDownloadable(module: CoreCourseAnyModuleData, courseId: number): Promise { - const workshop = await AddonModWorkshop.getWorkshop(courseId, module.id, { - readingStrategy: CoreSitesReadingStrategy.PREFER_CACHE, - }); - - const accessData = await AddonModWorkshop.getWorkshopAccessInformation(workshop.id, { cmId: module.id }); - - // Check if workshop is setup by phase. - return accessData.canswitchphase || workshop.phase > AddonModWorkshopPhase.PHASE_SETUP; - } - - /** - * @inheritdoc - */ - prefetch(module: CoreCourseAnyModuleData, courseId: number): Promise { - return this.prefetchPackage(module, courseId, (siteId) => this.prefetchWorkshop(module, courseId, siteId)); - } - - /** - * Retrieves all the grades reports for all the groups and then returns only unique grades. - * - * @param workshopId Workshop ID. - * @param groups Array of groups in the activity. - * @param cmId Module ID. - * @param siteId Site ID. If not defined, current site. - * @returns All unique entries. - */ - protected async getAllGradesReport( - workshopId: number, - groups: CoreGroup[], - cmId: number, - siteId: string, - ): Promise { - const promises: Promise[] = []; - - groups.forEach((group) => { - promises.push(AddonModWorkshop.fetchAllGradeReports(workshopId, { groupId: group.id, cmId, siteId })); - }); - - const grades = await Promise.all(promises); - const uniqueGrades: Record = {}; - - grades.forEach((groupGrades) => { - groupGrades.forEach((grade) => { - if (grade.submissionid) { - uniqueGrades[grade.submissionid] = grade; - } - }); - }); - - return CoreUtils.objectToArray(uniqueGrades); - } - - /** - * Prefetch a workshop. - * - * @param module The module object returned by WS. - * @param courseId Course ID the module belongs to. - * @param siteId Site ID. - * @returns Promise resolved when done. - */ - protected async prefetchWorkshop(module: CoreCourseAnyModuleData, courseId: number, siteId: string): Promise { - const userIds: number[] = []; - const commonOptions = { - readingStrategy: CoreSitesReadingStrategy.ONLY_NETWORK, - siteId, - }; - const modOptions = { - cmId: module.id, - ...commonOptions, // Include all common options. - }; - - const site = await CoreSites.getSite(siteId); - const currentUserId = site.getUserId(); - - // Prefetch the workshop data. - const info = await this.getWorkshopInfoHelper(module, courseId, commonOptions); - if (!info.workshop) { - // It would throw an exception so it would not happen. - return; - } - - const workshop = info.workshop; - const promises: Promise[] = []; - const assessmentIds: number[] = []; - - promises.push(CoreFilepool.addFilesToQueue(siteId, info.files, this.component, module.id)); - - promises.push(AddonModWorkshop.getWorkshopAccessInformation(workshop.id, modOptions).then(async (access) => { - const phases = await AddonModWorkshop.getUserPlanPhases(workshop.id, modOptions); - - // Get submission phase info. - const submissionPhase = phases[AddonModWorkshopPhase.PHASE_SUBMISSION]; - const canSubmit = AddonModWorkshopHelper.canSubmit(workshop, access, submissionPhase.tasks); - const canAssess = AddonModWorkshopHelper.canAssess(workshop, access); - const promises2: Promise[] = []; - - if (canSubmit) { - promises2.push(AddonModWorkshop.getSubmissions(workshop.id, modOptions)); - // Add userId to the profiles to prefetch. - userIds.push(currentUserId); - } - - let reportPromise: Promise = Promise.resolve(); - if (access.canviewallsubmissions && workshop.phase >= AddonModWorkshopPhase.PHASE_SUBMISSION) { - // eslint-disable-next-line promise/no-nesting - reportPromise = this.getAllGradesReport(workshop.id, info.groups, module.id, siteId).then((grades) => { - grades.forEach((grade) => { - userIds.push(grade.userid); - grade.submissiongradeoverby && userIds.push(grade.submissiongradeoverby); - - grade.reviewedby && grade.reviewedby.forEach((assessment) => { - userIds.push(assessment.userid); - assessmentIds[assessment.assessmentid] = assessment.assessmentid; - }); - - grade.reviewerof && grade.reviewerof.forEach((assessment) => { - userIds.push(assessment.userid); - assessmentIds[assessment.assessmentid] = assessment.assessmentid; - }); - }); - - return; - }); - } - - if (workshop.phase >= AddonModWorkshopPhase.PHASE_ASSESSMENT && canAssess) { - // Wait the report promise to finish to override assessments array if needed. - reportPromise = reportPromise.finally(async () => { - const revAssessments = await AddonModWorkshopHelper.getReviewerAssessments(workshop.id, { - userId: currentUserId, - cmId: module.id, - siteId, - }); - - let files: CoreWSExternalFile[] = []; // Files in each submission. - - revAssessments.forEach((assessment) => { - if (assessment.submission?.authorid == currentUserId) { - promises.push(AddonModWorkshop.getAssessment( - workshop.id, - assessment.id, - modOptions, - )); - } - userIds.push(assessment.reviewerid); - userIds.push(assessment.gradinggradeoverby); - assessmentIds[assessment.id] = assessment.id; - - files = files.concat(assessment.submission?.attachmentfiles || []) - .concat(assessment.submission?.contentfiles || []); - }); - - await CoreFilepool.addFilesToQueue(siteId, files, this.component, module.id); - }); - } - - reportPromise = reportPromise.finally(() => { - if (assessmentIds.length > 0) { - return Promise.all(assessmentIds.map((assessmentId) => - AddonModWorkshop.getAssessmentForm(workshop.id, assessmentId, modOptions))); - } - }); - promises2.push(reportPromise); - - if (workshop.phase == AddonModWorkshopPhase.PHASE_CLOSED) { - promises2.push(AddonModWorkshop.getGrades(workshop.id, modOptions)); - if (access.canviewpublishedsubmissions) { - promises2.push(AddonModWorkshop.getSubmissions(workshop.id, modOptions)); - } - } - - await Promise.all(promises2); - - return; - })); - - // Add Basic Info to manage links. - promises.push(CoreCourse.getModuleBasicInfoByInstance(workshop.id, 'workshop', { siteId })); - promises.push(CoreCourse.getModuleBasicGradeInfo(module.id, siteId)); - - // Get course data, needed to determine upload max size if it's configured to be course limit. - promises.push(CoreUtils.ignoreErrors(CoreCourses.getCourseByField('id', courseId, siteId))); - - await Promise.all(promises); - - // Prefetch user profiles. - await CoreUser.prefetchProfiles(userIds, courseId, siteId); - } - - /** - * @inheritdoc - */ - async sync(module: CoreCourseAnyModuleData, courseId: number, siteId?: string): Promise { - return AddonModWorkshopSync.syncWorkshop(module.instance, siteId); - } + name = ADDON_MOD_WORKSHOP_PREFETCH_NAME; + modName = ADDON_MOD_WORKSHOP_PREFETCH_MODNAME; + component = ADDON_MOD_WORKSHOP_PREFETCH_COMPONENT; + updatesNames = ADDON_MOD_WORKSHOP_PREFETCH_UPDATE_NAMES; } -export const AddonModWorkshopPrefetchHandler = makeSingleton(AddonModWorkshopPrefetchHandlerService); /** - * Options to pass to getWorkshopInfoHelper. + * Get prefetch handler instance. + * + * @returns Prefetch handler. */ -export type AddonModWorkshopGetInfoOptions = CoreSitesCommonWSOptions & { - omitFail?: boolean; // True to always return even if fails. -}; +export function getPrefetchHandlerInstance(): CoreCourseModulePrefetchHandler { + const lazyHandler = asyncInstance(async () => { + const { AddonModWorkshopPrefetchHandler } = await import('./prefetch-lazy'); + + return AddonModWorkshopPrefetchHandler.instance; + }); + + lazyHandler.setEagerInstance(new AddonModWorkshopPrefetchHandlerService()); + + return lazyHandler; +} diff --git a/src/addons/mod/workshop/services/handlers/sync-cron-lazy.ts b/src/addons/mod/workshop/services/handlers/sync-cron-lazy.ts new file mode 100644 index 000000000..cbe5eb693 --- /dev/null +++ b/src/addons/mod/workshop/services/handlers/sync-cron-lazy.ts @@ -0,0 +1,44 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Injectable } from '@angular/core'; +import { CoreCronHandler } from '@services/cron'; +import { makeSingleton } from '@singletons'; +import { AddonModWorkshopSync } from '../workshop-sync'; +import { AddonModWorkshopSyncCronHandlerService } from './sync-cron'; + +/** + * Synchronization cron handler. + */ +@Injectable({ providedIn: 'root' }) +export class AddonModWorkshopSyncCronHandlerLazyService + extends AddonModWorkshopSyncCronHandlerService + implements CoreCronHandler { + + /** + * @inheritdoc + */ + execute(siteId?: string, force?: boolean): Promise { + return AddonModWorkshopSync.syncAllWorkshops(siteId, force); + } + + /** + * @inheritdoc + */ + getInterval(): number { + return AddonModWorkshopSync.syncInterval; + } + +} +export const AddonModWorkshopSyncCronHandler = makeSingleton(AddonModWorkshopSyncCronHandlerLazyService); diff --git a/src/addons/mod/workshop/services/handlers/sync-cron.ts b/src/addons/mod/workshop/services/handlers/sync-cron.ts index d23811ab3..11cf1cc1a 100644 --- a/src/addons/mod/workshop/services/handlers/sync-cron.ts +++ b/src/addons/mod/workshop/services/handlers/sync-cron.ts @@ -12,32 +12,29 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Injectable } from '@angular/core'; +import { asyncInstance } from '@/core/utils/async-instance'; +import { ADDON_MOD_WORKSHOP_SYNC_CRON_NAME } from '@addons/mod/workshop/constants'; import { CoreCronHandler } from '@services/cron'; -import { makeSingleton } from '@singletons'; -import { AddonModWorkshopSync } from '../workshop-sync'; -/** - * Synchronization cron handler. - */ -@Injectable({ providedIn: 'root' }) -export class AddonModWorkshopSyncCronHandlerService implements CoreCronHandler { +export class AddonModWorkshopSyncCronHandlerService { - name = 'AddonModWorkshopSyncCronHandler'; - - /** - * @inheritdoc - */ - execute(siteId?: string, force?: boolean): Promise { - return AddonModWorkshopSync.syncAllWorkshops(siteId, force); - } - - /** - * @inheritdoc - */ - getInterval(): number { - return AddonModWorkshopSync.syncInterval; - } + name = ADDON_MOD_WORKSHOP_SYNC_CRON_NAME; } -export const AddonModWorkshopSyncCronHandler = makeSingleton(AddonModWorkshopSyncCronHandlerService); + +/** + * Get cron handler instance. + * + * @returns Cron handler. + */ +export function getCronHandlerInstance(): CoreCronHandler { + const lazyHandler = asyncInstance(async () => { + const { AddonModWorkshopSyncCronHandler } = await import('./sync-cron-lazy'); + + return AddonModWorkshopSyncCronHandler.instance; + }); + + lazyHandler.setEagerInstance(new AddonModWorkshopSyncCronHandlerService()); + + return lazyHandler; +} diff --git a/src/addons/mod/workshop/services/workshop-helper.ts b/src/addons/mod/workshop/services/workshop-helper.ts index 3fc6f6011..c868bfd6e 100644 --- a/src/addons/mod/workshop/services/workshop-helper.ts +++ b/src/addons/mod/workshop/services/workshop-helper.ts @@ -29,7 +29,6 @@ import { AddonModWorkshopExampleMode, AddonModWorkshopPhase, AddonModWorkshopUserOptions, - AddonModWorkshopProvider, AddonModWorkshopData, AddonModWorkshop, AddonModWorkshopSubmissionData, @@ -42,6 +41,7 @@ import { AddonModWorkshopGetAssessmentFormFieldsParsedData, } from './workshop'; import { AddonModWorkshopOffline, AddonModWorkshopOfflineSubmission } from './workshop-offline'; +import { ADDON_MOD_WORKSHOP_COMPONENT } from '@addons/mod/workshop/constants'; /** * Helper to gather some common functions for workshop. @@ -293,7 +293,7 @@ export class AddonModWorkshopHelperProvider { return this.storeSubmissionFiles(workshopId, files, siteId); } - return CoreFileUploader.uploadOrReuploadFiles(files, AddonModWorkshopProvider.COMPONENT, workshopId, siteId); + return CoreFileUploader.uploadOrReuploadFiles(files, ADDON_MOD_WORKSHOP_COMPONENT, workshopId, siteId); } /** @@ -403,7 +403,7 @@ export class AddonModWorkshopHelperProvider { return this.storeAssessmentFiles(workshopId, assessmentId, files, siteId); } - return CoreFileUploader.uploadOrReuploadFiles(files, AddonModWorkshopProvider.COMPONENT, workshopId, siteId); + return CoreFileUploader.uploadOrReuploadFiles(files, ADDON_MOD_WORKSHOP_COMPONENT, workshopId, siteId); } /** diff --git a/src/addons/mod/workshop/services/workshop-sync.ts b/src/addons/mod/workshop/services/workshop-sync.ts index 3e345020b..06b46dfa4 100644 --- a/src/addons/mod/workshop/services/workshop-sync.ts +++ b/src/addons/mod/workshop/services/workshop-sync.ts @@ -28,7 +28,6 @@ import { CoreEvents } from '@singletons/events'; import { AddonModWorkshop, AddonModWorkshopAction, AddonModWorkshopData, - AddonModWorkshopProvider, AddonModWorkshopSubmissionType, } from './workshop'; import { AddonModWorkshopHelper } from './workshop-helper'; @@ -38,6 +37,7 @@ import { AddonModWorkshopOffline, AddonModWorkshopOfflineEvaluateSubmission, AddonModWorkshopOfflineSubmission, } from './workshop-offline'; +import { ADDON_MOD_WORKSHOP_COMPONENT } from '@addons/mod/workshop/constants'; /** * Service to sync workshops. @@ -136,7 +136,7 @@ export class AddonModWorkshopSyncProvider extends CoreSyncBaseProvider( @@ -345,7 +345,7 @@ export class AddonModWorkshopProvider { const preSets: CoreSiteWSPreSets = { cacheKey: this.getWorkshopAccessInformationDataCacheKey(workshopId), - component: AddonModWorkshopProvider.COMPONENT, + component: ADDON_MOD_WORKSHOP_COMPONENT, componentId: options.cmId, ...CoreSites.getReadingStrategyPreSets(options.readingStrategy), // Include reading strategy preSets. }; @@ -390,7 +390,7 @@ export class AddonModWorkshopProvider { const preSets: CoreSiteWSPreSets = { cacheKey: this.getUserPlanDataCacheKey(workshopId), updateFrequency: CoreSite.FREQUENCY_OFTEN, - component: AddonModWorkshopProvider.COMPONENT, + component: ADDON_MOD_WORKSHOP_COMPONENT, componentId: options.cmId, ...CoreSites.getReadingStrategyPreSets(options.readingStrategy), // Include reading strategy preSets. }; @@ -437,7 +437,7 @@ export class AddonModWorkshopProvider { const preSets: CoreSiteWSPreSets = { cacheKey: this.getSubmissionsDataCacheKey(workshopId, userId, groupId), updateFrequency: CoreSite.FREQUENCY_OFTEN, - component: AddonModWorkshopProvider.COMPONENT, + component: ADDON_MOD_WORKSHOP_COMPONENT, componentId: options.cmId, ...CoreSites.getReadingStrategyPreSets(options.readingStrategy), // Include reading strategy preSets. }; @@ -483,7 +483,7 @@ export class AddonModWorkshopProvider { const preSets: CoreSiteWSPreSets = { cacheKey: this.getSubmissionDataCacheKey(workshopId, submissionId), - component: AddonModWorkshopProvider.COMPONENT, + component: ADDON_MOD_WORKSHOP_COMPONENT, componentId: options.cmId, ...CoreSites.getReadingStrategyPreSets(options.readingStrategy), // Include reading strategy preSets. }; @@ -523,7 +523,7 @@ export class AddonModWorkshopProvider { const preSets: CoreSiteWSPreSets = { cacheKey: this.getGradesDataCacheKey(workshopId), - component: AddonModWorkshopProvider.COMPONENT, + component: ADDON_MOD_WORKSHOP_COMPONENT, componentId: options.cmId, ...CoreSites.getReadingStrategyPreSets(options.readingStrategy), // Include reading strategy preSets. }; @@ -567,7 +567,7 @@ export class AddonModWorkshopProvider { const preSets: CoreSiteWSPreSets = { cacheKey: this.getGradesReportDataCacheKey(workshopId, options.groupId), updateFrequency: CoreSite.FREQUENCY_OFTEN, - component: AddonModWorkshopProvider.COMPONENT, + component: ADDON_MOD_WORKSHOP_COMPONENT, componentId: options.cmId, ...CoreSites.getReadingStrategyPreSets(options.readingStrategy), // Include reading strategy preSets. }; @@ -663,7 +663,7 @@ export class AddonModWorkshopProvider { const preSets: CoreSiteWSPreSets = { cacheKey: this.getSubmissionAssessmentsDataCacheKey(workshopId, submissionId), - component: AddonModWorkshopProvider.COMPONENT, + component: ADDON_MOD_WORKSHOP_COMPONENT, componentId: options.cmId, ...CoreSites.getReadingStrategyPreSets(options.readingStrategy), // Include reading strategy preSets. }; @@ -967,7 +967,7 @@ export class AddonModWorkshopProvider { const preSets: CoreSiteWSPreSets = { cacheKey: this.getReviewerAssessmentsDataCacheKey(workshopId, options.userId), - component: AddonModWorkshopProvider.COMPONENT, + component: ADDON_MOD_WORKSHOP_COMPONENT, componentId: options.cmId, ...CoreSites.getReadingStrategyPreSets(options.readingStrategy), // Include reading strategy preSets. }; @@ -1017,7 +1017,7 @@ export class AddonModWorkshopProvider { const preSets: CoreSiteWSPreSets = { cacheKey: this.getAssessmentDataCacheKey(workshopId, assessmentId), - component: AddonModWorkshopProvider.COMPONENT, + component: ADDON_MOD_WORKSHOP_COMPONENT, componentId: options.cmId, ...CoreSites.getReadingStrategyPreSets(options.readingStrategy), // Include reading strategy preSets. }; @@ -1065,7 +1065,7 @@ export class AddonModWorkshopProvider { const preSets: CoreSiteWSPreSets = { cacheKey: this.getAssessmentFormDataCacheKey(workshopId, assessmentId, mode), updateFrequency: CoreSite.FREQUENCY_RARELY, - component: AddonModWorkshopProvider.COMPONENT, + component: ADDON_MOD_WORKSHOP_COMPONENT, componentId: options.cmId, ...CoreSites.getReadingStrategyPreSets(options.readingStrategy), // Include reading strategy preSets. }; @@ -1454,7 +1454,7 @@ export class AddonModWorkshopProvider { await CoreCourseLogHelper.log( 'mod_workshop_view_workshop', params, - AddonModWorkshopProvider.COMPONENT, + ADDON_MOD_WORKSHOP_COMPONENT, id, siteId, ); @@ -1476,7 +1476,7 @@ export class AddonModWorkshopProvider { await CoreCourseLogHelper.log( 'mod_workshop_view_submission', params, - AddonModWorkshopProvider.COMPONENT, + ADDON_MOD_WORKSHOP_COMPONENT, workshopId, siteId, ); diff --git a/src/addons/mod/workshop/workshop.module.ts b/src/addons/mod/workshop/workshop.module.ts index 92ec3d3f9..2a12db7d2 100644 --- a/src/addons/mod/workshop/workshop.module.ts +++ b/src/addons/mod/workshop/workshop.module.ts @@ -27,13 +27,14 @@ import { AddonWorkshopAssessmentStrategyDelegateService } from './services/asses import { ADDON_MOD_WORKSHOP_OFFLINE_SITE_SCHEMA } from './services/database/workshop'; import { AddonModWorkshopIndexLinkHandler } from './services/handlers/index-link'; import { AddonModWorkshopListLinkHandler } from './services/handlers/list-link'; -import { AddonModWorkshopModuleHandler, AddonModWorkshopModuleHandlerService } from './services/handlers/module'; -import { AddonModWorkshopPrefetchHandler } from './services/handlers/prefetch'; -import { AddonModWorkshopSyncCronHandler } from './services/handlers/sync-cron'; +import { AddonModWorkshopModuleHandler } from './services/handlers/module'; import { AddonModWorkshopProvider } from './services/workshop'; import { AddonModWorkshopHelperProvider } from './services/workshop-helper'; import { AddonModWorkshopOfflineProvider } from './services/workshop-offline'; import { AddonModWorkshopSyncProvider } from './services/workshop-sync'; +import { ADDON_MOD_WORKSHOP_COMPONENT, ADDON_MOD_WORKSHOP_PAGE_NAME } from '@addons/mod/workshop/constants'; +import { getCronHandlerInstance } from '@addons/mod/workshop/services/handlers/sync-cron'; +import { getPrefetchHandlerInstance } from '@addons/mod/workshop/services/handlers/prefetch'; // List of providers (without handlers). export const ADDON_MOD_WORKSHOP_SERVICES: Type[] = [ @@ -46,7 +47,7 @@ export const ADDON_MOD_WORKSHOP_SERVICES: Type[] = [ const routes: Routes = [ { - path: AddonModWorkshopModuleHandlerService.PAGE_NAME, + path: ADDON_MOD_WORKSHOP_PAGE_NAME, loadChildren: () => import('./workshop-lazy.module').then(m => m.AddonModWorkshopLazyModule), }, ]; @@ -68,12 +69,12 @@ const routes: Routes = [ multi: true, useValue: () => { CoreCourseModuleDelegate.registerHandler(AddonModWorkshopModuleHandler.instance); - CoreCourseModulePrefetchDelegate.registerHandler(AddonModWorkshopPrefetchHandler.instance); - CoreCronDelegate.register(AddonModWorkshopSyncCronHandler.instance); + CoreCourseModulePrefetchDelegate.registerHandler(getPrefetchHandlerInstance()); + CoreCronDelegate.register(getCronHandlerInstance()); CoreContentLinksDelegate.registerHandler(AddonModWorkshopIndexLinkHandler.instance); CoreContentLinksDelegate.registerHandler(AddonModWorkshopListLinkHandler.instance); - CoreCourseHelper.registerModuleReminderClick(AddonModWorkshopProvider.COMPONENT); + CoreCourseHelper.registerModuleReminderClick(ADDON_MOD_WORKSHOP_COMPONENT); }, }, ], diff --git a/src/core/features/native/services/native.ts b/src/core/features/native/services/native.ts index 9b98c0a62..2220a621d 100644 --- a/src/core/features/native/services/native.ts +++ b/src/core/features/native/services/native.ts @@ -23,7 +23,7 @@ import { AsyncInstance, asyncInstance } from '@/core/utils/async-instance'; @Injectable({ providedIn: 'root' }) export class CoreNativeService { - private plugins: Partial>> = {}; + private plugins: Partial> = {}; /** * Get a native plugin instance. diff --git a/src/core/services/cron.ts b/src/core/services/cron.ts index 811eeaf40..01c3505b9 100644 --- a/src/core/services/cron.ts +++ b/src/core/services/cron.ts @@ -212,14 +212,14 @@ export class CoreCronDelegateService { * @param name Handler's name. * @returns Handler's interval. */ - protected getHandlerInterval(name: string): number { + protected async getHandlerInterval(name: string): Promise { if (this.handlers[name] === undefined) { // Invalid, return default. return CoreCronDelegateService.DEFAULT_INTERVAL; } // Don't allow intervals lower than the minimum. - const handlerInterval = this.handlers[name].getInterval?.(); + const handlerInterval = await this.handlers[name].getInterval?.(); if (!handlerInterval) { return CoreCronDelegateService.DEFAULT_INTERVAL; @@ -365,8 +365,7 @@ export class CoreCronDelegateService { if (!timeToNextExecution) { // Get last execution time to check when do we need to execute it. const lastExecution = await this.getHandlerLastExecutionTime(name); - - const interval = this.getHandlerInterval(name); + const interval = await this.getHandlerInterval(name); timeToNextExecution = lastExecution + interval - Date.now(); } @@ -486,7 +485,7 @@ export interface CoreCronHandler { * * @returns Interval time (in milliseconds). */ - getInterval?(): number; + getInterval?(): number | Promise; /** * Check whether the process uses network or not. True if not defined. diff --git a/src/core/utils/async-instance.ts b/src/core/utils/async-instance.ts index 977a8b83e..40352cb1c 100644 --- a/src/core/utils/async-instance.ts +++ b/src/core/utils/async-instance.ts @@ -14,19 +14,28 @@ import { CorePromisedValue } from '@classes/promised-value'; +// eslint-disable-next-line @typescript-eslint/ban-types +type AsyncObject = object; + /** * Create a wrapper to hold an asynchronous instance. * * @param lazyConstructor Constructor to use the first time the instance is needed. * @returns Asynchronous instance wrapper. */ -function createAsyncInstanceWrapper(lazyConstructor?: () => T | Promise): AsyncInstanceWrapper { - let promisedInstance: CorePromisedValue | null = null; +function createAsyncInstanceWrapper( + lazyConstructor?: () => TInstance | Promise, +): AsyncInstanceWrapper { + let promisedInstance: CorePromisedValue | null = null; + let eagerInstance: TEagerInstance; return { get instance() { return promisedInstance?.value ?? undefined; }, + get eagerInstance() { + return eagerInstance; + }, async getInstance() { if (!promisedInstance) { promisedInstance = new CorePromisedValue(); @@ -54,6 +63,9 @@ function createAsyncInstanceWrapper(lazyConstructor?: () => T | Promise): promisedInstance.resolve(instance); }, + setEagerInstance(instance) { + eagerInstance = instance; + }, setLazyConstructor(constructor) { if (!promisedInstance) { lazyConstructor = constructor; @@ -81,12 +93,14 @@ function createAsyncInstanceWrapper(lazyConstructor?: () => T | Promise): /** * Asynchronous instance wrapper. */ -export interface AsyncInstanceWrapper { - instance?: T; - getInstance(): Promise; - getProperty

(property: P): Promise; - setInstance(instance: T): void; - setLazyConstructor(lazyConstructor: () => T | Promise): void; +export interface AsyncInstanceWrapper { + instance?: TInstance; + eagerInstance?: TEagerInstance; + getInstance(): Promise; + getProperty

(property: P): Promise; + setInstance(instance: TInstance): void; + setEagerInstance(eagerInstance: TEagerInstance): void; + setLazyConstructor(lazyConstructor: () => TInstance | Promise): void; resetInstance(): void; } @@ -107,9 +121,10 @@ export type AsyncMethod = * All methods are converted to their asynchronous version, and properties are available asynchronously using * the getProperty method. */ -export type AsyncInstance = AsyncInstanceWrapper & { - [k in keyof T]: AsyncMethod; -}; +export type AsyncInstance = + AsyncInstanceWrapper & TEagerInstance & { + [k in keyof TInstance]: AsyncMethod; + }; /** * Create an asynchronous instance proxy, where all methods will be callable directly but will become asynchronous. If the @@ -118,8 +133,10 @@ export type AsyncInstance = AsyncInstanceWrapper & { * @param lazyConstructor Constructor to use the first time the instance is needed. * @returns Asynchronous instance. */ -export function asyncInstance(lazyConstructor?: () => T | Promise): AsyncInstance { - const wrapper = createAsyncInstanceWrapper(lazyConstructor); +export function asyncInstance( + lazyConstructor?: () => TInstance | Promise, +): AsyncInstance { + const wrapper = createAsyncInstanceWrapper(lazyConstructor); return new Proxy(wrapper, { get: (target, property, receiver) => { @@ -127,11 +144,19 @@ export function asyncInstance(lazyConstructor?: () => T | Promise): AsyncI return Reflect.get(target, property, receiver); } + if (wrapper.instance && property in wrapper.instance) { + return Reflect.get(wrapper.instance, property, receiver); + } + + if (wrapper.eagerInstance && property in wrapper.eagerInstance) { + return Reflect.get(wrapper.eagerInstance, property, receiver); + } + return async (...args: unknown[]) => { const instance = await wrapper.getInstance(); return instance[property](...args); }; }, - }) as AsyncInstance; + }) as AsyncInstance; }