diff --git a/scripts/langindex.json b/scripts/langindex.json
index 57ee82499..df750b6bf 100644
--- a/scripts/langindex.json
+++ b/scripts/langindex.json
@@ -681,6 +681,7 @@
"addon.mod_h5pactivity.attempt_success_fail": "h5pactivity",
"addon.mod_h5pactivity.attempt_success_pass": "h5pactivity",
"addon.mod_h5pactivity.attempt_success_unknown": "h5pactivity",
+ "addon.mod_h5pactivity.attempts": "h5pactivity",
"addon.mod_h5pactivity.attempts_none": "h5pactivity",
"addon.mod_h5pactivity.completion": "h5pactivity",
"addon.mod_h5pactivity.downloadh5pfile": "local_moodlemobileapp",
@@ -692,12 +693,15 @@
"addon.mod_h5pactivity.modulenameplural": "h5pactivity",
"addon.mod_h5pactivity.myattempts": "h5pactivity",
"addon.mod_h5pactivity.no_compatible_track": "h5pactivity",
+ "addon.mod_h5pactivity.noparticipants": "h5pactivity",
"addon.mod_h5pactivity.offlinedisabledwarning": "local_moodlemobileapp",
"addon.mod_h5pactivity.outcome": "h5pactivity",
"addon.mod_h5pactivity.previewmode": "h5pactivity",
"addon.mod_h5pactivity.result_fill-in": "h5pactivity",
"addon.mod_h5pactivity.result_other": "h5pactivity",
+ "addon.mod_h5pactivity.review_attempts": "local_moodlemobileapp",
"addon.mod_h5pactivity.review_my_attempts": "h5pactivity",
+ "addon.mod_h5pactivity.review_user_attempts": "h5pactivity",
"addon.mod_h5pactivity.score": "h5pactivity",
"addon.mod_h5pactivity.score_out_of": "h5pactivity",
"addon.mod_h5pactivity.startdate": "h5pactivity",
diff --git a/src/addons/mod/h5pactivity/components/index/addon-mod-h5pactivity-index.html b/src/addons/mod/h5pactivity/components/index/addon-mod-h5pactivity-index.html
index a3a164dfb..f88b40b88 100644
--- a/src/addons/mod/h5pactivity/components/index/addon-mod-h5pactivity-index.html
+++ b/src/addons/mod/h5pactivity/components/index/addon-mod-h5pactivity-index.html
@@ -5,6 +5,10 @@
[priority]="1000" [content]="'addon.mod_h5pactivity.review_my_attempts' | translate" (action)="viewMyAttempts()"
iconAction="stats-chart">
+
+
diff --git a/src/addons/mod/h5pactivity/components/index/index.ts b/src/addons/mod/h5pactivity/components/index/index.ts
index e2f69620e..37989271f 100644
--- a/src/addons/mod/h5pactivity/components/index/index.ts
+++ b/src/addons/mod/h5pactivity/components/index/index.ts
@@ -79,6 +79,7 @@ export class AddonModH5PActivityIndexComponent extends CoreCourseModuleMainActiv
trackComponent?: string; // Component for tracking.
hasOffline = false;
isOpeningPage = false;
+ canViewAllAttempts = false;
protected listeningResize = false;
protected fetchContentDefaultError = 'addon.mod_h5pactivity.errorgetactivity';
@@ -137,6 +138,8 @@ export class AddonModH5PActivityIndexComponent extends CoreCourseModuleMainActiv
]);
this.trackComponent = this.accessInfo?.cansubmit ? AddonModH5PActivityProvider.TRACK_COMPONENT : '';
+ this.canViewAllAttempts = !!this.h5pActivity.enabletracking && !!this.accessInfo?.canreviewattempts &&
+ AddonModH5PActivity.canGetUsersAttemptsInSite();
if (this.h5pActivity.package && this.h5pActivity.package[0]) {
// The online player should use the original file, not the trusted one.
@@ -377,7 +380,7 @@ export class AddonModH5PActivityIndexComponent extends CoreCourseModuleMainActiv
}
/**
- * Go to view user events.
+ * Go to view user attempts.
*/
async viewMyAttempts(): Promise {
this.isOpeningPage = true;
@@ -392,6 +395,21 @@ export class AddonModH5PActivityIndexComponent extends CoreCourseModuleMainActiv
}
}
+ /**
+ * Go to view all user attempts.
+ */
+ async viewAllAttempts(): Promise {
+ this.isOpeningPage = true;
+
+ try {
+ await CoreNavigator.navigateToSitePath(
+ `${AddonModH5PActivityModuleHandlerService.PAGE_NAME}/${this.courseId}/${this.module.id}/users`,
+ );
+ } finally {
+ this.isOpeningPage = false;
+ }
+ }
+
/**
* Treat an iframe message event.
*
diff --git a/src/addons/mod/h5pactivity/h5pactivity-lazy.module.ts b/src/addons/mod/h5pactivity/h5pactivity-lazy.module.ts
index 477958871..3ac3e9562 100644
--- a/src/addons/mod/h5pactivity/h5pactivity-lazy.module.ts
+++ b/src/addons/mod/h5pactivity/h5pactivity-lazy.module.ts
@@ -36,6 +36,11 @@ const routes: Routes = [
loadChildren: () => import('./pages/attempt-results/attempt-results.module')
.then( m => m.AddonModH5PActivityAttemptResultsPageModule),
},
+ {
+ path: ':courseId/:cmId/users',
+ loadChildren: () => import('./pages/users-attempts/users-attempts.module')
+ .then( m => m.AddonModH5PActivityUsersAttemptsPageModule),
+ },
];
@NgModule({
diff --git a/src/addons/mod/h5pactivity/lang.json b/src/addons/mod/h5pactivity/lang.json
index 3ac109635..55044c8dd 100644
--- a/src/addons/mod/h5pactivity/lang.json
+++ b/src/addons/mod/h5pactivity/lang.json
@@ -11,6 +11,7 @@
"attempt_success_fail": "Fail",
"attempt_success_pass": "Pass",
"attempt_success_unknown": "Not reported",
+ "attempts": "Attempts",
"attempts_none": "This user has no attempts to display.",
"completion": "Completion",
"downloadh5pfile": "Download H5P file",
@@ -22,15 +23,18 @@
"modulenameplural": "H5P",
"myattempts": "My attempts",
"no_compatible_track": "This interaction ({{$a}}) does not provide tracking information or the tracking\n provided is not compatible with the current activity version.",
+ "noparticipants": "No participants to display",
"offlinedisabledwarning": "You need to be online to view the H5P package.",
"outcome": "Outcome",
"previewmode": "This content is displayed in preview mode. No attempt tracking will be stored.",
"result_fill-in": "Fill-in text",
"result_other": "Unknown interaction type",
+ "review_attempts": "View all attempts",
"review_my_attempts": "View my attempts",
+ "review_user_attempts": "View user attempts ({{$a}})",
"score": "Score",
"score_out_of": "{{$a.rawscore}} out of {{$a.maxscore}}",
"startdate": "Start date",
"totalscore": "Total score",
"viewattempt": "View attempt {{$a}}"
-}
\ No newline at end of file
+}
diff --git a/src/addons/mod/h5pactivity/pages/user-attempts/user-attempts.html b/src/addons/mod/h5pactivity/pages/user-attempts/user-attempts.html
index 01bcfe080..31ca8fcd6 100644
--- a/src/addons/mod/h5pactivity/pages/user-attempts/user-attempts.html
+++ b/src/addons/mod/h5pactivity/pages/user-attempts/user-attempts.html
@@ -17,7 +17,7 @@
+ [attr.aria-label]="user.fullname" button detail="true">
{{ user.fullname }}
diff --git a/src/addons/mod/h5pactivity/pages/users-attempts/users-attempts.html b/src/addons/mod/h5pactivity/pages/users-attempts/users-attempts.html
new file mode 100644
index 000000000..15a9557e6
--- /dev/null
+++ b/src/addons/mod/h5pactivity/pages/users-attempts/users-attempts.html
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ user.user.fullname }}
+
+
+
+ {{ user.attempts[user.attempts.length - 1].timemodified | coreFormatDate:'strftimedatetimeshort' }}
+
+
+
+
+ {{ 'core.percentagenumber' | translate: {$a: user.score} }}
+
+
+
+ {{user.attempts.length}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/addons/mod/h5pactivity/pages/users-attempts/users-attempts.module.ts b/src/addons/mod/h5pactivity/pages/users-attempts/users-attempts.module.ts
new file mode 100644
index 000000000..c09f58954
--- /dev/null
+++ b/src/addons/mod/h5pactivity/pages/users-attempts/users-attempts.module.ts
@@ -0,0 +1,38 @@
+// (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 { NgModule } from '@angular/core';
+import { RouterModule, Routes } from '@angular/router';
+
+import { CoreSharedModule } from '@/core/shared.module';
+import { AddonModH5PActivityUsersAttemptsPage } from './users-attempts';
+
+const routes: Routes = [
+ {
+ path: '',
+ component: AddonModH5PActivityUsersAttemptsPage,
+ },
+];
+
+@NgModule({
+ imports: [
+ RouterModule.forChild(routes),
+ CoreSharedModule,
+ ],
+ declarations: [
+ AddonModH5PActivityUsersAttemptsPage,
+ ],
+ exports: [RouterModule],
+})
+export class AddonModH5PActivityUsersAttemptsPageModule {}
diff --git a/src/addons/mod/h5pactivity/pages/users-attempts/users-attempts.ts b/src/addons/mod/h5pactivity/pages/users-attempts/users-attempts.ts
new file mode 100644
index 000000000..7dee400fe
--- /dev/null
+++ b/src/addons/mod/h5pactivity/pages/users-attempts/users-attempts.ts
@@ -0,0 +1,202 @@
+// (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 { Component, OnInit } from '@angular/core';
+import { CoreUser, CoreUserProfile } from '@features/user/services/user';
+import { IonRefresher } from '@ionic/angular';
+
+import { CoreNavigator } from '@services/navigator';
+import { CoreDomUtils } from '@services/utils/dom';
+import { CoreUtils } from '@services/utils/utils';
+import {
+ AddonModH5PActivity,
+ AddonModH5PActivityData,
+ AddonModH5PActivityProvider,
+ AddonModH5PActivityUserAttempts,
+} from '../../services/h5pactivity';
+
+/**
+ * Page that displays all users that can attempt an H5P activity.
+ */
+@Component({
+ selector: 'page-addon-mod-h5pactivity-users-attempts',
+ templateUrl: 'users-attempts.html',
+})
+export class AddonModH5PActivityUsersAttemptsPage implements OnInit {
+
+ loaded = false;
+ courseId!: number;
+ cmId!: number;
+ h5pActivity?: AddonModH5PActivityData;
+ users: AddonModH5PActivityUserAttemptsFormatted[] = [];
+ fetchMoreUsersFailed = false;
+ canLoadMore = false;
+
+ protected page = 0;
+
+ /**
+ * @inheritdoc
+ */
+ async ngOnInit(): Promise {
+ this.courseId = CoreNavigator.getRouteNumberParam('courseId')!;
+ this.cmId = CoreNavigator.getRouteNumberParam('cmId')!;
+
+ try {
+ await this.fetchData();
+
+ await AddonModH5PActivity.logViewReport(this.h5pActivity!.id, this.h5pActivity!.name);
+ } catch (error) {
+ CoreDomUtils.showErrorModalDefault(error, 'Error loading attempts.');
+ } finally {
+ this.loaded = true;
+ }
+ }
+
+ /**
+ * Refresh the data.
+ *
+ * @param refresher Refresher.
+ */
+ doRefresh(refresher: IonRefresher): void {
+ this.refreshData().finally(() => {
+ refresher.complete();
+ });
+ }
+
+ /**
+ * Get quiz data and attempt data.
+ *
+ * @param refresh Whether user is refreshing data.
+ * @return Promise resolved when done.
+ */
+ protected async fetchData(refresh?: boolean): Promise {
+ this.h5pActivity = await AddonModH5PActivity.getH5PActivity(this.courseId, this.cmId);
+
+ await Promise.all([
+ this.fetchUsers(refresh),
+ ]);
+ }
+
+ /**
+ * Get users.
+ *
+ * @param refresh Whether user is refreshing data.
+ * @return Promise resolved when done.
+ */
+ protected async fetchUsers(refresh?: boolean): Promise {
+ if (refresh) {
+ this.page = 0;
+ }
+
+ const result = await AddonModH5PActivity.getUsersAttempts(this.h5pActivity!.id, {
+ cmId: this.cmId,
+ page: this.page,
+ });
+
+ const formattedUsers = await this.formatUsers(result.users);
+
+ if (this.page === 0) {
+ this.users = formattedUsers;
+ } else {
+ this.users = this.users.concat(formattedUsers);
+ }
+
+ this.canLoadMore = result.canLoadMore;
+ this.page++;
+ }
+
+ /**
+ * Format users data.
+ *
+ * @param users Users to format.
+ * @return Formatted users.
+ */
+ protected async formatUsers(users: AddonModH5PActivityUserAttempts[]): Promise {
+ return await Promise.all(users.map(async (user: AddonModH5PActivityUserAttemptsFormatted) => {
+ user.user = await CoreUser.getProfile(user.userid, this.courseId, true);
+
+ // Calculate the score of the user.
+ if (this.h5pActivity!.grademethod === AddonModH5PActivityProvider.GRADEMANUAL) {
+ // No score.
+ } else if (this.h5pActivity!.grademethod === AddonModH5PActivityProvider.GRADEAVERAGEATTEMPT) {
+ if (user.attempts.length) {
+ // Calculate the average.
+ const sumScores = user.attempts.reduce((sumScores, attempt) =>
+ sumScores + attempt.rawscore * 100 / attempt.maxscore, 0);
+
+ user.score = Math.round(sumScores / user.attempts.length);
+ }
+ } else if (user.scored?.attempts[0]) {
+ // Only a single attempt used to calculate the grade. Use it.
+ user.score = Math.round(user.scored.attempts[0].rawscore * 100 / user.scored.attempts[0].maxscore);
+ }
+
+ return user;
+ }));
+ }
+
+ /**
+ * Load a new batch of users.
+ *
+ * @param complete Completion callback.
+ */
+ async fetchMoreUsers(complete: () => void): Promise {
+ try {
+ await this.fetchUsers(false);
+ } catch (error) {
+ CoreDomUtils.showErrorModalDefault(error, 'Error loading more users');
+
+ this.fetchMoreUsersFailed = true;
+ }
+
+ complete();
+ }
+
+ /**
+ * Refresh the data.
+ *
+ * @return Promise resolved when done.
+ */
+ protected async refreshData(): Promise {
+ const promises = [
+ AddonModH5PActivity.invalidateActivityData(this.courseId),
+ ];
+
+ if (this.h5pActivity) {
+ promises.push(AddonModH5PActivity.invalidateAllUsersAttempts(this.h5pActivity.id));
+ }
+
+ await CoreUtils.ignoreErrors(Promise.all(promises));
+
+ await this.fetchData(true);
+ }
+
+ /**
+ * Open the page to view a user attempts.
+ *
+ * @param user User to open.
+ */
+ openUser(user: AddonModH5PActivityUserAttemptsFormatted): void {
+ CoreNavigator.navigate(`../userattempts/${user.userid}`);
+ }
+
+}
+
+/**
+ * User attempts data with some calculated data.
+ */
+type AddonModH5PActivityUserAttemptsFormatted = AddonModH5PActivityUserAttempts & {
+ user?: CoreUserProfile;
+ score?: number;
+};
diff --git a/src/addons/mod/h5pactivity/services/h5pactivity.ts b/src/addons/mod/h5pactivity/services/h5pactivity.ts
index 28927c099..7f16095fc 100644
--- a/src/addons/mod/h5pactivity/services/h5pactivity.ts
+++ b/src/addons/mod/h5pactivity/services/h5pactivity.ts
@@ -38,6 +38,40 @@ export class AddonModH5PActivityProvider {
static readonly COMPONENT = 'mmaModH5PActivity';
static readonly TRACK_COMPONENT = 'mod_h5pactivity'; // Component for tracking.
+ static readonly USERS_PER_PAGE = 20;
+
+ // Grade type constants.
+ static readonly GRADEMANUAL = 0; // No automathic grading using attempt results.
+ static readonly GRADEHIGHESTATTEMPT = 1; // Use highest attempt results for grading.
+ static readonly GRADEAVERAGEATTEMPT = 2; // Use average attempt results for grading.
+ static readonly GRADELASTATTEMPT = 3; // Use last attempt results for grading.
+ static readonly GRADEFIRSTATTEMPT = 4; // Use first attempt results for grading.
+
+ /**
+ * Check if a certain site allows viewing list of users and their attempts.
+ *
+ * @param site Site ID. If not defined, use current site.
+ * @return Whether can view users.
+ * @since 3.11
+ */
+ async canGetUsersAttempts(siteId?: string): Promise {
+ const site = await CoreSites.getSite(siteId);
+
+ return this.canGetUsersAttemptsInSite(site);
+ }
+
+ /**
+ * Check if a certain site allows viewing list of users and their attempts.
+ *
+ * @param site Site. If not defined, use current site.
+ * @return Whether can view users.
+ * @since 3.11
+ */
+ canGetUsersAttemptsInSite(site?: CoreSite): boolean {
+ site = site || CoreSites.getCurrentSite();
+
+ return !!site?.wsAvailable('mod_h5pactivity_get_user_attempts');
+ }
/**
* Format an attempt's data.
@@ -168,6 +202,122 @@ export class AddonModH5PActivityProvider {
}
}
+ /**
+ * Get all pages of users attempts.
+ *
+ * @param id Activity ID.
+ * @param options Other options.
+ * @return Promise resolved with the list of user.
+ */
+ async getAllUsersAttempts(
+ id: number,
+ options?: AddonModH5PActivityGetAllUsersAttemptsOptions,
+ ): Promise {
+
+ const optionsWithPage: AddonModH5PActivityGetAllUsersAttemptsOptions = {
+ ...options,
+ page: 0,
+ };
+ let canLoadMore = true;
+ let users: AddonModH5PActivityUserAttempts[] = [];
+
+ while (canLoadMore) {
+ try {
+ const result = await this.getUsersAttempts(id, optionsWithPage);
+
+ optionsWithPage.page = optionsWithPage.page! + 1;
+ users = users.concat(result.users);
+ canLoadMore = result.canLoadMore;
+ } catch (error) {
+ if (optionsWithPage.dontFailOnError) {
+ return users;
+ }
+
+ throw error;
+ }
+ }
+
+ return users;
+ }
+
+ /**
+ * Get list of users and their attempts.
+ *
+ * @param id H5P Activity ID.
+ * @param options Options.
+ * @return Promise resolved with list of users and whether can load more attempts.
+ * @since 3.11
+ */
+ async getUsersAttempts(
+ id: number,
+ options?: AddonModH5PActivityGetUsersAttemptsOptions,
+ ): Promise<{users: AddonModH5PActivityUserAttempts[]; canLoadMore: boolean}> {
+ options = options || {};
+ options.page = options.page || 0;
+ options.perPage = options.perPage ?? AddonModH5PActivityProvider.USERS_PER_PAGE;
+
+ const site = await CoreSites.getSite(options.siteId);
+
+ const params: AddonModH5pactivityGetUserAttemptsWSParams = {
+ h5pactivityid: id,
+ page: options.page,
+ perpage: options.perPage === 0 ? 0 : options.perPage + 1, // Get 1 more to be able to know if there are more to load.
+ sortorder: options.sortOrder,
+ firstinitial: options.firstInitial,
+ lastinitial: options.lastInitial,
+ };
+ const preSets: CoreSiteWSPreSets = {
+ cacheKey: this.getUsersAttemptsCacheKey(id, options),
+ updateFrequency: CoreSite.FREQUENCY_SOMETIMES,
+ component: AddonModH5PActivityProvider.COMPONENT,
+ componentId: options.cmId,
+ ...CoreSites.getReadingStrategyPreSets(options.readingStrategy), // Include reading strategy preSets.
+ };
+
+ const response = await site.read(
+ 'mod_h5pactivity_get_user_attempts',
+ params,
+ preSets,
+ );
+
+ if (response.warnings?.[0]) {
+ throw new CoreWSError(response.warnings[0]);
+ }
+
+ let canLoadMore = false;
+ if (options.perPage > 0) {
+ canLoadMore = response.usersattempts.length > options.perPage;
+ response.usersattempts = response.usersattempts.slice(0, options.perPage);
+ }
+
+ return {
+ canLoadMore: canLoadMore,
+ users: response.usersattempts.map(userAttempts => this.formatUserAttempts(userAttempts)),
+ };
+ }
+
+ /**
+ * Get cache key for get users attempts WS calls.
+ *
+ * @param id Instance ID.
+ * @param attemptsIds Attempts IDs.
+ * @return Cache key.
+ */
+ protected getUsersAttemptsCacheKey(id: number, options: AddonModH5PActivityGetUsersAttemptsOptions): string {
+ return this.getUsersAttemptsCommonCacheKey(id) + `:${options.page}:${options.perPage}` +
+ `:${options.sortOrder || ''}:${options.firstInitial || ''}:${options.lastInitial || ''}`;
+ }
+
+ /**
+ * Get common cache key for get users attempts WS calls.
+ *
+ * @param id Instance ID.
+ * @return Cache key.
+ */
+ protected getUsersAttemptsCommonCacheKey(id: number): string {
+ return ROOT_CACHE_KEY + 'userAttempts:' + id;
+ }
+
/**
* Get cache key for results WS calls.
*
@@ -455,26 +605,56 @@ export class AddonModH5PActivityProvider {
): Promise {
const site = await CoreSites.getSite(options.siteId);
+ const userId = options.userId || site.getUserId();
- const params: AddonModH5pactivityGetAttemptsWSParams = {
- h5pactivityid: id,
- userids: [options.userId || site.getUserId()],
- };
- const preSets: CoreSiteWSPreSets = {
- cacheKey: this.getUserAttemptsCacheKey(id, params.userids!),
- updateFrequency: CoreSite.FREQUENCY_SOMETIMES,
- component: AddonModH5PActivityProvider.COMPONENT,
- componentId: options.cmId,
- ...CoreSites.getReadingStrategyPreSets(options.readingStrategy), // Include reading strategy preSets.
- };
+ try {
+ const params: AddonModH5pactivityGetAttemptsWSParams = {
+ h5pactivityid: id,
+ userids: [userId],
+ };
+ const preSets: CoreSiteWSPreSets = {
+ cacheKey: this.getUserAttemptsCacheKey(id, params.userids!),
+ updateFrequency: CoreSite.FREQUENCY_SOMETIMES,
+ component: AddonModH5PActivityProvider.COMPONENT,
+ componentId: options.cmId,
+ ...CoreSites.getReadingStrategyPreSets(options.readingStrategy), // Include reading strategy preSets.
+ };
- const response = await site.read('mod_h5pactivity_get_attempts', params, preSets);
+ const response = await site.read(
+ 'mod_h5pactivity_get_attempts',
+ params,
+ preSets,
+ );
- if (response.warnings?.[0]) {
- throw new CoreWSError(response.warnings[0]); // Cannot view user attempts.
+ if (response.warnings?.[0]) {
+ throw new CoreWSError(response.warnings[0]); // Cannot view user attempts.
+ }
+
+ return this.formatUserAttempts(response.usersattempts[0]);
+ } catch (error) {
+ if (CoreUtils.isWebServiceError(error)) {
+ throw error;
+ }
+
+ try {
+ // Check if the full list of users is cached. If so, get the user attempts from there.
+ const users = await this.getAllUsersAttempts(id, {
+ ...options,
+ readingStrategy: CoreSitesReadingStrategy.ONLY_CACHE,
+ dontFailOnError: true,
+ });
+
+ const user = users.find(user => user.userid === userId);
+ if (!user) {
+ throw error;
+ }
+
+ return this.formatUserAttempts(user);
+ } catch {
+ throw error;
+ }
}
- return this.formatUserAttempts(response.usersattempts[0]);
}
/**
@@ -532,16 +712,16 @@ export class AddonModH5PActivityProvider {
}
/**
- * Invalidates all users attempts for H5P activity.
+ * Invalidates list of users for H5P activity.
*
* @param id Activity ID.
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved when the data is invalidated.
*/
- async invalidateAllUserAttempts(id: number, siteId?: string): Promise {
+ async invalidateAllUsersAttempts(id: number, siteId?: string): Promise {
const site = await CoreSites.getSite(siteId);
- await site.invalidateWsCacheForKey(this.getUserAttemptsCommonCacheKey(id));
+ await site.invalidateWsCacheForKeyStartingWith(this.getUsersAttemptsCommonCacheKey(id));
}
/**
@@ -896,6 +1076,45 @@ export type AddonModH5PActivityViewReportOptions = {
siteId?: string; // Site ID. If not defined, current site.
};
+/**
+ * Params of mod_h5pactivity_get_user_attempts WS.
+ */
+export type AddonModH5pactivityGetUserAttemptsWSParams = {
+ h5pactivityid: number; // H5p activity instance id.
+ sortorder?: string; // Sort by this element: id, firstname.
+ page?: number; // Current page.
+ perpage?: number; // Items per page.
+ firstinitial?: string; // Users whose first name starts with firstinitial.
+ lastinitial?: string; // Users whose last name starts with lastinitial.
+};
+
+/**
+ * Data returned by mod_h5pactivity_get_user_attempts WS.
+ */
+export type AddonModH5pactivityGetUserAttemptsWSResponse = {
+ activityid: number; // Activity course module ID.
+ usersattempts: AddonModH5PActivityWSUserAttempts[]; // The complete users attempts list.
+ warnings?: CoreWSExternalWarning[];
+};
+
+/**
+ * Options for getUsersAttempts.
+ */
+export type AddonModH5PActivityGetUsersAttemptsOptions = CoreCourseCommonModWSOptions & {
+ sortOrder?: string; // Sort by this element: id, firstname.
+ page?: number; // Current page. Defaults to 0.
+ perPage?: number; // Items per page. Defaults to USERS_PER_PAGE.
+ firstInitial?: string; // Users whose first name starts with firstInitial.
+ lastInitial?: string; // Users whose last name starts with lastInitial.
+};
+
+/**
+ * Options for getAllUsersAttempts.
+ */
+export type AddonModH5PActivityGetAllUsersAttemptsOptions = AddonModH5PActivityGetUsersAttemptsOptions & {
+ dontFailOnError?: boolean; // If true the function will return the users it's able to retrieve, until a call fails.
+};
+
declare module '@singletons/events' {
/**
diff --git a/src/addons/mod/h5pactivity/services/handlers/prefetch.ts b/src/addons/mod/h5pactivity/services/handlers/prefetch.ts
index df1f199ef..14ecc15cb 100644
--- a/src/addons/mod/h5pactivity/services/handlers/prefetch.ts
+++ b/src/addons/mod/h5pactivity/services/handlers/prefetch.ts
@@ -147,20 +147,31 @@ export class AddonModH5PActivityPrefetchHandlerService extends CoreCourseActivit
siteId,
});
+ const options = {
+ cmId: h5pActivity.coursemodule,
+ readingStrategy: CoreSitesReadingStrategy.ONLY_NETWORK,
+ siteId: siteId,
+ };
+
if (!accessInfo.canreviewattempts) {
// Not a teacher, prefetch user attempts and the current user profile.
const site = await CoreSites.getSite(siteId);
- const options = {
- cmId: h5pActivity.coursemodule,
- readingStrategy: CoreSitesReadingStrategy.ONLY_NETWORK,
- siteId: siteId,
- };
-
await Promise.all([
AddonModH5PActivity.getAllAttemptsResults(h5pActivity.id, options),
CoreUser.prefetchProfiles([site.getUserId()], h5pActivity.course, siteId),
]);
+ } else {
+ // It's a teacher, get all attempts if possible.
+ const canGetUsers = await AddonModH5PActivity.canGetUsersAttempts(siteId);
+ if (!canGetUsers) {
+ return;
+ }
+
+ const users = await AddonModH5PActivity.getAllUsersAttempts(h5pActivity.id, options);
+
+ const userIds = users.map(user => user.userid);
+ await CoreUser.prefetchProfiles(userIds, h5pActivity.course, siteId);
}
}