From dc85f9604606b649f5328b8ba0cc5cc00819c02f Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Wed, 3 Jun 2020 14:07:32 +0200 Subject: [PATCH] MOBILE-3413 h5pactivity: Prefetch attempts data --- .../index/addon-mod-h5pactivity-index.html | 2 +- .../pages/attempt-results/attempt-results.ts | 8 +- .../pages/user-attempts/user-attempts.ts | 6 +- .../mod/h5pactivity/providers/h5pactivity.ts | 251 ++++++++++++------ .../h5pactivity/providers/prefetch-handler.ts | 37 ++- 5 files changed, 220 insertions(+), 84 deletions(-) diff --git a/src/addon/mod/h5pactivity/components/index/addon-mod-h5pactivity-index.html b/src/addon/mod/h5pactivity/components/index/addon-mod-h5pactivity-index.html index 823d475cf..f2acd6230 100644 --- a/src/addon/mod/h5pactivity/components/index/addon-mod-h5pactivity-index.html +++ b/src/addon/mod/h5pactivity/components/index/addon-mod-h5pactivity-index.html @@ -1,7 +1,7 @@ - + diff --git a/src/addon/mod/h5pactivity/pages/attempt-results/attempt-results.ts b/src/addon/mod/h5pactivity/pages/attempt-results/attempt-results.ts index fdd495c7b..96b99fc16 100644 --- a/src/addon/mod/h5pactivity/pages/attempt-results/attempt-results.ts +++ b/src/addon/mod/h5pactivity/pages/attempt-results/attempt-results.ts @@ -100,7 +100,7 @@ export class AddonModH5PActivityAttemptResultsPage implements OnInit { * @return Promise resolved when done. */ protected async fetchAttempt(): Promise { - this.attempt = await AddonModH5PActivity.instance.getResults(this.h5pActivityId, this.attemptId); + this.attempt = await AddonModH5PActivity.instance.getAttemptResults(this.h5pActivityId, this.attemptId); } /** @@ -109,7 +109,11 @@ export class AddonModH5PActivityAttemptResultsPage implements OnInit { * @return Promise resolved when done. */ protected async fetchUserProfile(): Promise { - this.user = await CoreUser.instance.getProfile(this.attempt.userid, this.courseId, true); + try { + this.user = await CoreUser.instance.getProfile(this.attempt.userid, this.courseId, true); + } catch (error) { + // Ignore errors. + } } /** diff --git a/src/addon/mod/h5pactivity/pages/user-attempts/user-attempts.ts b/src/addon/mod/h5pactivity/pages/user-attempts/user-attempts.ts index 7e33e03cd..cc8499405 100644 --- a/src/addon/mod/h5pactivity/pages/user-attempts/user-attempts.ts +++ b/src/addon/mod/h5pactivity/pages/user-attempts/user-attempts.ts @@ -110,7 +110,11 @@ export class AddonModH5PActivityUserAttemptsPage implements OnInit { * @return Promise resolved when done. */ protected async fetchUserProfile(): Promise { - this.user = await CoreUser.instance.getProfile(this.userId, this.courseId, true); + try { + this.user = await CoreUser.instance.getProfile(this.userId, this.courseId, true); + } catch (error) { + // Ignore errors. + } } /** diff --git a/src/addon/mod/h5pactivity/providers/h5pactivity.ts b/src/addon/mod/h5pactivity/providers/h5pactivity.ts index b57500a5c..240ff537c 100644 --- a/src/addon/mod/h5pactivity/providers/h5pactivity.ts +++ b/src/addon/mod/h5pactivity/providers/h5pactivity.ts @@ -17,6 +17,7 @@ import { Injectable } from '@angular/core'; import { CoreSites } from '@providers/sites'; import { CoreWSExternalWarning, CoreWSExternalFile } from '@providers/ws'; import { CoreTimeUtils } from '@providers/utils/time'; +import { CoreUtils } from '@providers/utils/utils'; import { CoreSite, CoreSiteWSPreSets } from '@classes/site'; import { CoreCourseLogHelper } from '@core/course/providers/log-helper'; import { CoreH5P } from '@core/h5p/providers/h5p'; @@ -64,9 +65,9 @@ export class AddonModH5PActivityProvider { protected formatAttemptResults(attempt: AddonModH5PActivityWSAttemptResults): AddonModH5PActivityAttemptResults { const formattedAttempt: AddonModH5PActivityAttemptResults = this.formatAttempt(attempt); - for (const i in formattedAttempt.results) { - formattedAttempt.results[i] = this.formatResult(formattedAttempt.results[i]); - } + formattedAttempt.results = formattedAttempt.results.map((result) => { + return this.formatResult(result); + }); return formattedAttempt; } @@ -80,14 +81,15 @@ export class AddonModH5PActivityProvider { protected formatUserAttempts(data: AddonModH5PActivityWSUserAttempts): AddonModH5PActivityUserAttempts { const formatted: AddonModH5PActivityUserAttempts = data; - for (const i in formatted.attempts) { - formatted.attempts[i] = this.formatAttempt(formatted.attempts[i]); - } + formatted.attempts = formatted.attempts.map((attempt) => { + return this.formatAttempt(attempt); + }); if (formatted.scored) { - for (const i in formatted.scored.attempts) { - formatted.scored.attempts[i] = this.formatAttempt(formatted.scored.attempts[i]); - } + + formatted.scored.attempts = formatted.scored.attempts.map((attempt) => { + return this.formatAttempt(attempt); + }); } return formatted; @@ -137,6 +139,153 @@ export class AddonModH5PActivityProvider { return site.read('mod_h5pactivity_get_h5pactivity_access_information', params, preSets); } + /** + * Get attempt results for all user attempts. + * + * @param id Activity ID. + * @param options Other options. + * @return Promise resolved with the results of the attempt. + */ + async getAllAttemptsResults(id: number, options?: AddonModH5PActivityGetAttemptResultsOptions) + : Promise { + + const userAttempts = await AddonModH5PActivity.instance.getUserAttempts(id, options); + + const attemptIds = userAttempts.attempts.map((attempt) => { + return attempt.id; + }); + + if (attemptIds.length) { + // Get all the attempts with a single call. + return AddonModH5PActivity.instance.getAttemptsResults(id, attemptIds, options); + } else { + // No attempts. + return { + activityid: id, + attempts: [], + warnings: [], + }; + } + } + + /** + * Get cache key for results WS calls. + * + * @param id Instance ID. + * @param attemptsIds Attempts IDs. + * @return Cache key. + */ + protected getAttemptResultsCacheKey(id: number, attemptsIds: number[]): string { + return this.getAttemptResultsCommonCacheKey(id) + ':' + JSON.stringify(attemptsIds); + } + + /** + * Get common cache key for results WS calls. + * + * @param id Instance ID. + * @return Cache key. + */ + protected getAttemptResultsCommonCacheKey(id: number): string { + return this.ROOT_CACHE_KEY + 'results:' + id; + } + + /** + * Get attempt results. + * + * @param id Activity ID. + * @param attemptId Attempt ID. + * @param options Other options. + * @return Promise resolved with the results of the attempt. + */ + async getAttemptResults(id: number, attemptId: number, options?: AddonModH5PActivityGetAttemptResultsOptions) + : Promise { + + options = options || {}; + + const site = await CoreSites.instance.getSite(options.siteId); + + const params = { + h5pactivityid: id, + attemptids: [attemptId], + }; + const preSets: CoreSiteWSPreSets = { + cacheKey: this.getAttemptResultsCacheKey(id, params.attemptids), + updateFrequency: CoreSite.FREQUENCY_SOMETIMES, + }; + + if (options.forceCache) { + preSets.omitExpires = true; + } else if (options.ignoreCache) { + preSets.getFromCache = false; + preSets.emergencyCache = false; + } + + try { + const response: AddonModH5PActivityGetResultsResult = await site.read('mod_h5pactivity_get_results', params, preSets); + + return this.formatAttemptResults(response.attempts[0]); + } catch (error) { + if (CoreUtils.instance.isWebServiceError(error)) { + throw error; + } + + // Check if the full list of results is cached. If so, get the results from there. + options.forceCache = true; + + const attemptsResults = await AddonModH5PActivity.instance.getAllAttemptsResults(id, options); + + const attempt = attemptsResults.attempts.find((attempt) => { + return attempt.id == attemptId; + }); + + if (!attempt) { + throw error; + } + + return attempt; + } + } + + /** + * Get attempts results. + * + * @param id Activity ID. + * @param attemptsIds Attempts IDs. + * @param options Other options. + * @return Promise resolved with all the attempts. + */ + async getAttemptsResults(id: number, attemptsIds: number[], options?: AddonModH5PActivityGetAttemptResultsOptions) + : Promise { + + options = options || {}; + + const site = await CoreSites.instance.getSite(options.siteId); + + const params = { + h5pactivityid: id, + attemptids: attemptsIds, + }; + const preSets: CoreSiteWSPreSets = { + cacheKey: this.getAttemptResultsCommonCacheKey(id), + updateFrequency: CoreSite.FREQUENCY_SOMETIMES, + }; + + if (options.forceCache) { + preSets.omitExpires = true; + } else if (options.ignoreCache) { + preSets.getFromCache = false; + preSets.emergencyCache = false; + } + + const response: AddonModH5PActivityGetResultsResult = await site.read('mod_h5pactivity_get_results', params, preSets); + + response.attempts = response.attempts.map((attempt) => { + return this.formatAttemptResults(attempt); + }); + + return response; + } + /** * Get deployed file from an H5P activity instance. * @@ -244,61 +393,6 @@ export class AddonModH5PActivityProvider { return this.getH5PActivityByField(courseId, 'id', id, forceCache, siteId); } - /** - * Get cache key for results WS calls. - * - * @param id Instance ID. - * @param attemptsIds Attempts IDs. - * @return Cache key. - */ - protected getResultsCacheKey(id: number, attemptsIds: number[]): string { - return this.getResultsCommonCacheKey(id) + ':' + JSON.stringify(attemptsIds); - } - - /** - * Get common cache key for results WS calls. - * - * @param id Instance ID. - * @return Cache key. - */ - protected getResultsCommonCacheKey(id: number): string { - return this.ROOT_CACHE_KEY + 'results:' + id; - } - - /** - * Get attempt results. - * - * @param id Activity ID. - * @param attemptId Attempt ID. - * @param options Other options. - * @return Promise resolved with the attempts of the user. - */ - async getResults(id: number, attemptId: number, options?: AddonModH5PActivityGetResultsOptions) - : Promise { - - options = options || {}; - - const site = await CoreSites.instance.getSite(options.siteId); - - const params = { - h5pactivityid: id, - attemptids: [attemptId], - }; - const preSets: CoreSiteWSPreSets = { - cacheKey: this.getResultsCacheKey(id, params.attemptids), - updateFrequency: CoreSite.FREQUENCY_SOMETIMES, - }; - - if (options.ignoreCache) { - preSets.getFromCache = false; - preSets.emergencyCache = false; - } - - const response: AddonModH5PActivityGetResultsResult = await site.read('mod_h5pactivity_get_results', params, preSets); - - return this.formatAttemptResults(response.attempts[0]); - } - /** * Get cache key for attemps WS calls. * @@ -342,7 +436,9 @@ export class AddonModH5PActivityProvider { updateFrequency: CoreSite.FREQUENCY_SOMETIMES, }; - if (options.ignoreCache) { + if (options.forceCache) { + preSets.omitExpires = true; + } else if (options.ignoreCache) { preSets.getFromCache = false; preSets.emergencyCache = false; } @@ -389,7 +485,7 @@ export class AddonModH5PActivityProvider { async invalidateAllResults(id: number, siteId?: string): Promise { const site = await CoreSites.instance.getSite(siteId); - await site.invalidateWsCacheForKey(this.getResultsCommonCacheKey(id)); + await site.invalidateWsCacheForKey(this.getAttemptResultsCommonCacheKey(id)); } /** @@ -403,7 +499,7 @@ export class AddonModH5PActivityProvider { async invalidateAttemptResults(id: number, attemptId: number, siteId?: string): Promise { const site = await CoreSites.instance.getSite(siteId); - await site.invalidateWsCacheForKey(this.getResultsCacheKey(id, [attemptId])); + await site.invalidateWsCacheForKey(this.getAttemptResultsCacheKey(id, [attemptId])); } /** @@ -633,6 +729,15 @@ export type AddonModH5PActivityUserAttempts = { }; }; +/** + * Attempts results with some calculated data. + */ +export type AddonModH5PActivityAttemptsResults = { + activityid: number; // Activity course module ID. + attempts: AddonModH5PActivityAttemptResults[]; // The complete attempts list. + warnings?: CoreWSExternalWarning[]; +}; + /** * Attempt with some calculated data. */ @@ -658,18 +763,16 @@ export type AddonModH5PActivityGetDeployedFileOptions = { }; /** - * Options to pass to getResults function. + * Options to pass to getAttemptResults function. */ -export type AddonModH5PActivityGetResultsOptions = { +export type AddonModH5PActivityGetAttemptResultsOptions = { + forceCache?: boolean; // Whether to force cache. If not cached, it will call the WS. ignoreCache?: boolean; // Whether to ignore cache. Will fail if offline or server down. siteId?: string; // Site ID. If not defined, current site. + userId?: number; // User ID. If not defined, user of the site. }; /** * Options to pass to getAttempts function. */ -export type AddonModH5PActivityGetAttemptsOptions = { - ignoreCache?: boolean; // Whether to ignore cache. Will fail if offline or server down. - siteId?: string; // Site ID. If not defined, current site. - userId?: number; // User ID. If not defined, user of the site. -}; +export type AddonModH5PActivityGetAttemptsOptions = AddonModH5PActivityGetAttemptResultsOptions; diff --git a/src/addon/mod/h5pactivity/providers/prefetch-handler.ts b/src/addon/mod/h5pactivity/providers/prefetch-handler.ts index ccb03a168..85a804baf 100644 --- a/src/addon/mod/h5pactivity/providers/prefetch-handler.ts +++ b/src/addon/mod/h5pactivity/providers/prefetch-handler.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Injectable, Injector } from '@angular/core'; +import { Injectable } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; import { CoreAppProvider } from '@providers/app'; import { CoreFilepoolProvider } from '@providers/filepool'; @@ -26,7 +26,7 @@ import { CoreCourseActivityPrefetchHandlerBase } from '@core/course/classes/acti import { CoreFilterHelperProvider } from '@core/filter/providers/helper'; import { CoreH5PHelper } from '@core/h5p/classes/helper'; import { CoreH5P } from '@core/h5p/providers/h5p'; -import { CoreUserProvider } from '@core/user/providers/user'; +import { CoreUser } from '@core/user/providers/user'; import { AddonModH5PActivity, AddonModH5PActivityProvider, AddonModH5PActivityData } from './h5pactivity'; /** @@ -47,9 +47,7 @@ export class AddonModH5PActivityPrefetchHandler extends CoreCourseActivityPrefet sitesProvider: CoreSitesProvider, domUtils: CoreDomUtilsProvider, filterHelper: CoreFilterHelperProvider, - pluginFileDelegate: CorePluginFileDelegate, - protected userProvider: CoreUserProvider, - protected injector: Injector) { + pluginFileDelegate: CorePluginFileDelegate) { super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils, filterHelper, pluginFileDelegate); @@ -137,7 +135,7 @@ export class AddonModH5PActivityPrefetchHandler extends CoreCourseActivityPrefet const introFiles = this.getIntroFilesFromInstance(module, h5pActivity); await Promise.all([ - AddonModH5PActivity.instance.getAccessInformation(h5pActivity.id, true, siteId), + this.prefetchWSData(h5pActivity, siteId), this.filepoolProvider.addFilesToQueue(siteId, introFiles, AddonModH5PActivityProvider.COMPONENT, module.id), this.prefetchMainFile(module, h5pActivity, siteId), ]); @@ -163,4 +161,31 @@ export class AddonModH5PActivityPrefetchHandler extends CoreCourseActivityPrefet await this.filepoolProvider.addFilesToQueue(siteId, [deployedFile], AddonModH5PActivityProvider.COMPONENT, module.id); } + + /** + * Prefetch all the WebService data. + * + * @param h5pActivity Activity instance. + * @param siteId Site ID. + * @return Promise resolved when done. + */ + protected async prefetchWSData(h5pActivity: AddonModH5PActivityData, siteId: string): Promise { + + const accessInfo = await AddonModH5PActivity.instance.getAccessInformation(h5pActivity.id, true, siteId); + + if (!accessInfo.canreviewattempts) { + // Not a teacher, prefetch user attempts and the current user profile. + const site = await this.sitesProvider.getSite(siteId); + + const options = { + ignoreCache: true, + siteId: siteId, + }; + + await Promise.all([ + AddonModH5PActivity.instance.getAllAttemptsResults(h5pActivity.id, options), + CoreUser.instance.prefetchProfiles([site.getUserId()], h5pActivity.course, siteId), + ]); + } + } }