MOBILE-4368 analytics: Apply new analytics system to all pages

main
Dani Palou 2023-06-22 11:09:33 +02:00 committed by Alfonso Salces
parent 0598ec0062
commit 2d2cc2f5f9
131 changed files with 1973 additions and 546 deletions

View File

@ -104,10 +104,12 @@
"addon.calendar.currentmonth": "local_moodlemobileapp",
"addon.calendar.daynext": "calendar",
"addon.calendar.dayprev": "calendar",
"addon.calendar.dayviewtitle": "calendar",
"addon.calendar.defaultnotificationtime": "local_moodlemobileapp",
"addon.calendar.deleteallevents": "calendar",
"addon.calendar.deleteevent": "calendar",
"addon.calendar.deleteoneevent": "calendar",
"addon.calendar.detailedmonthviewtitle": "calendar",
"addon.calendar.durationminutes": "calendar",
"addon.calendar.durationnone": "calendar",
"addon.calendar.durationuntil": "calendar",
@ -369,6 +371,7 @@
"addon.mod_assign.gradelocked": "assign",
"addon.mod_assign.gradenotsynced": "local_moodlemobileapp",
"addon.mod_assign.gradeoutof": "assign",
"addon.mod_assign.grading": "assign",
"addon.mod_assign.gradingstatus": "assign",
"addon.mod_assign.groupsubmissionsettings": "assign",
"addon.mod_assign.hiddenuser": "assign",
@ -425,6 +428,7 @@
"addon.mod_assign.submittedlate": "assign",
"addon.mod_assign.submittedovertime": "assign",
"addon.mod_assign.submittedundertime": "assign",
"addon.mod_assign.subpagetitle": "assign",
"addon.mod_assign.syncblockedusercomponent": "local_moodlemobileapp",
"addon.mod_assign.timelimit": "assign",
"addon.mod_assign.timemodified": "assign",
@ -2235,6 +2239,7 @@
"core.play": "local_moodlemobileapp",
"core.previous": "moodle",
"core.proceed": "moodle",
"core.publicprofile": "moodle",
"core.pulltorefresh": "local_moodlemobileapp",
"core.qrscanner": "local_moodlemobileapp",
"core.question.answer": "question",

View File

@ -26,6 +26,8 @@ import { ActivatedRoute } from '@angular/router';
import { CoreSwipeNavigationItemsManager } from '@classes/items-management/swipe-navigation-items-manager';
import { AddonBadgesUserBadgesSource } from '@addons/badges/classes/user-badges-source';
import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
import { CoreTime } from '@singletons/time';
/**
* Page that displays the list of calendar events.
@ -38,6 +40,7 @@ export class AddonBadgesIssuedBadgePage implements OnInit, OnDestroy {
protected badgeHash = '';
protected userId!: number;
protected logView: (badge: AddonBadgesUserBadge) => void;
courseId = 0;
user?: CoreUserProfile;
@ -58,6 +61,16 @@ export class AddonBadgesIssuedBadgePage implements OnInit, OnDestroy {
);
this.badges = new CoreSwipeNavigationItemsManager(source);
this.logView = CoreTime.once((badge) => {
CoreAnalytics.logEvent({
type: CoreAnalyticsEventType.VIEW_ITEM,
ws: 'core_badges_view_user_badges',
name: badge.name,
data: { id: badge.uniquehash, category: 'badges' },
url: `/badges/badge.php?hash=${badge.uniquehash}`,
});
});
}
/**
@ -105,6 +118,8 @@ export class AddonBadgesIssuedBadgePage implements OnInit, OnDestroy {
this.course = undefined;
}
}
this.logView(badge);
} catch (message) {
CoreDomUtils.showErrorModalDefault(message, 'Error getting badge data.');
}

View File

@ -24,6 +24,9 @@ import { CoreNavigator } from '@services/navigator';
import { CoreListItemsManager } from '@classes/items-management/list-items-manager';
import { AddonBadgesUserBadgesSource } from '@addons/badges/classes/user-badges-source';
import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
import { CoreTime } from '@singletons/time';
import { Translate } from '@singletons';
/**
* Page that displays the list of calendar events.
@ -39,6 +42,8 @@ export class AddonBadgesUserBadgesPage implements AfterViewInit, OnDestroy {
@ViewChild(CoreSplitViewComponent) splitView!: CoreSplitViewComponent;
protected logView: () => void;
constructor() {
let courseId = CoreNavigator.getRouteNumberParam('courseId') ?? 0; // Use 0 for site badges.
const userId = CoreNavigator.getRouteNumberParam('userId') ?? CoreSites.getCurrentSiteUserId();
@ -52,6 +57,16 @@ export class AddonBadgesUserBadgesPage implements AfterViewInit, OnDestroy {
CoreRoutedItemsManagerSourcesTracker.getOrCreateSource(AddonBadgesUserBadgesSource, [courseId, userId]),
AddonBadgesUserBadgesPage,
);
this.logView = CoreTime.once(() => {
CoreAnalytics.logEvent({
type: CoreAnalyticsEventType.VIEW_ITEM_LIST,
ws: 'core_badges_view_user_badges',
name: Translate.instant('addon.badges.badges'),
data: { courseId: this.badges.getSource().COURSE_ID, category: 'badges' },
url: '/badges/mybadges.php',
});
});
}
/**
@ -95,6 +110,8 @@ export class AddonBadgesUserBadgesPage implements AfterViewInit, OnDestroy {
try {
await this.badges.reload();
this.logView();
} catch (message) {
CoreDomUtils.showErrorModalDefault(message, 'Error loading badges');

View File

@ -20,11 +20,14 @@ import { CoreMainMenuDeepLinkManager } from '@features/mainmenu/classes/deep-lin
import { CoreTag } from '@features/tag/services/tag';
import { CoreUser, CoreUserProfile } from '@features/user/services/user';
import { IonRefresher } from '@ionic/angular';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
import { CoreNavigator } from '@services/navigator';
import { CoreSites } from '@services/sites';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreTextUtils } from '@services/utils/text';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreUtils } from '@services/utils/utils';
import { CoreTime } from '@singletons/time';
/**
* Page that displays the list of blog entries.
@ -43,7 +46,7 @@ export class AddonBlogEntriesPage implements OnInit {
protected canLoadMoreEntries = false;
protected canLoadMoreUserEntries = true;
protected siteHomeId: number;
protected fetchSuccess = false;
protected logView: () => void;
loaded = false;
canLoadMore = false;
@ -61,6 +64,25 @@ export class AddonBlogEntriesPage implements OnInit {
constructor() {
this.currentUserId = CoreSites.getCurrentSiteUserId();
this.siteHomeId = CoreSites.getCurrentSiteHomeId();
this.logView = CoreTime.once(async () => {
await CoreUtils.ignoreErrors(AddonBlog.logView(this.filter));
CoreAnalytics.logEvent({
type: CoreAnalyticsEventType.VIEW_ITEM_LIST,
ws: 'core_blog_view_entries',
name: this.title,
data: {
...this.filter,
category: 'blog',
},
url: CoreUrlUtils.addParamsToUrl('/blog/index.php', {
...this.filter,
modid: this.filter.cmid,
cmid: undefined,
}),
});
});
}
/**
@ -200,10 +222,7 @@ export class AddonBlogEntriesPage implements OnInit {
await Promise.all(promises);
if (!this.fetchSuccess) {
this.fetchSuccess = true;
CoreUtils.ignoreErrors(AddonBlog.logView(this.filter));
}
this.logView();
} catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'addon.blog.errorloadentries', true);
this.loadMoreError = true; // Set to prevent infinite calls with infinite-loading.

View File

@ -14,7 +14,6 @@
import { Injectable } from '@angular/core';
import { CoreSite, CoreSiteWSPreSets } from '@classes/site';
import { CorePushNotifications } from '@features/pushnotifications/services/pushnotifications';
import { CoreTagItem } from '@features/tag/services/tag';
import { CoreSites } from '@services/sites';
import { CoreUtils } from '@services/utils/utils';
@ -104,8 +103,6 @@ export class AddonBlogProvider {
* @returns Promise to be resolved when done.
*/
async logView(filter: AddonBlogFilter = {}, siteId?: string): Promise<CoreStatusWithWarningsWSResponse> {
CorePushNotifications.logViewListEvent('blog', 'core_blog_view_entries', filter, siteId);
const site = await CoreSites.getSite(siteId);
const data: AddonBlogViewEntriesWSParams = {

View File

@ -49,6 +49,10 @@ import {
} from '@classes/items-management/swipe-slides-dynamic-items-manager-source';
import { CoreSwipeSlidesDynamicItemsManager } from '@classes/items-management/swipe-slides-dynamic-items-manager';
import moment from 'moment-timezone';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreTime } from '@singletons/time';
import { Translate } from '@singletons';
/**
* Component that displays a calendar.
@ -81,6 +85,7 @@ export class AddonCalendarCalendarComponent implements OnInit, DoCheck, OnDestro
// Observers and listeners.
protected undeleteEventObserver: CoreEventObserver;
protected managerUnsubscribe?: () => void;
protected logView: () => void;
constructor(differs: KeyValueDiffers) {
this.currentSiteId = CoreSites.getCurrentSiteId();
@ -107,6 +112,29 @@ export class AddonCalendarCalendarComponent implements OnInit, DoCheck, OnDestro
this.hiddenDiffer = this.hidden;
this.filterDiffer = differs.find(this.filter ?? {}).create();
this.logView = CoreTime.once(() => {
const month = this.manager?.getSelectedItem();
if (!month) {
return;
}
const params = {
course: this.filter?.courseId,
time: month.moment.unix(),
};
CoreAnalytics.logEvent({
type: CoreAnalyticsEventType.VIEW_ITEM_LIST,
ws: 'core_calendar_get_calendar_monthly_view',
name: Translate.instant('addon.calendar.detailedmonthviewtitle', { $a: this.periodName }),
data: {
...params,
category: 'calendar',
},
url: CoreUrlUtils.addParamsToUrl('/calendar/view.php?view=month', params),
});
});
}
@HostBinding('attr.hidden') get hiddenAttribute(): string | null {
@ -124,7 +152,7 @@ export class AddonCalendarCalendarComponent implements OnInit, DoCheck, OnDestro
const source = new AddonCalendarMonthSlidesItemsManagerSource(this, moment({
year: this.initialYear,
month: this.initialMonth ? this.initialMonth - 1 : undefined,
}));
}).startOf('month'));
this.manager = new CoreSwipeSlidesDynamicItemsManager(source);
this.managerUnsubscribe = this.manager.addListener({
onSelectedItemUpdated: (item) => {
@ -176,6 +204,8 @@ export class AddonCalendarCalendarComponent implements OnInit, DoCheck, OnDestro
await this.manager?.getSource().fetchData();
await this.manager?.getSource().load(this.manager?.getSelectedItem());
this.logView();
} catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'addon.calendar.errorloadevents', true);
}

View File

@ -25,6 +25,10 @@ import { AddonCalendarHelper, AddonCalendarFilter } from '../../services/calenda
import { AddonCalendarOffline } from '../../services/calendar-offline';
import { CoreCategoryData, CoreCourses } from '@features/courses/services/courses';
import { CoreConstants } from '@/core/constants';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreTime } from '@singletons/time';
import { Translate } from '@singletons';
/**
* Component that displays upcoming events.
@ -54,6 +58,7 @@ export class AddonCalendarUpcomingEventsComponent implements OnInit, DoCheck, On
protected lookAhead = 0;
protected timeFormat?: string;
protected differ: KeyValueDiffer<unknown, unknown>; // To detect changes in the data input.
protected logView: () => void;
// Observers.
protected undeleteEventObserver: CoreEventObserver;
@ -84,6 +89,23 @@ export class AddonCalendarUpcomingEventsComponent implements OnInit, DoCheck, On
);
this.differ = differs.find([]).create();
this.logView = CoreTime.once(() => {
const params = {
course: this.filter?.courseId,
};
CoreAnalytics.logEvent({
type: CoreAnalyticsEventType.VIEW_ITEM_LIST,
ws: 'core_calendar_get_calendar_upcoming_view',
name: Translate.instant('addon.calendar.upcomingevents'),
data: {
...params,
category: 'calendar',
},
url: CoreUrlUtils.addParamsToUrl('/calendar/view.php?view=upcoming', params),
});
});
}
/**
@ -148,8 +170,9 @@ export class AddonCalendarUpcomingEventsComponent implements OnInit, DoCheck, On
try {
await Promise.all(promises);
this.fetchEvents();
await this.fetchEvents();
this.logView();
} catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'addon.calendar.errorloadevents', true);
}

View File

@ -11,10 +11,12 @@
"currentmonth": "Current Month",
"daynext": "Next day",
"dayprev": "Previous day",
"dayviewtitle": "Day view: {{$a}}",
"defaultnotificationtime": "Default notification time",
"deleteallevents": "Delete all events",
"deleteevent": "Delete event",
"deleteoneevent": "Delete this event",
"detailedmonthviewtitle": "Detailed month view: {{$a}}",
"durationminutes": "Duration in minutes",
"durationnone": "Without duration",
"durationuntil": "Until",

View File

@ -33,7 +33,7 @@ import { CoreCategoryData, CoreCourses, CoreEnrolledCourseData } from '@features
import { CoreCoursesHelper } from '@features/courses/services/courses-helper';
import { AddonCalendarFilterComponent } from '../../components/filter/filter';
import moment from 'moment-timezone';
import { NgZone } from '@singletons';
import { NgZone, Translate } from '@singletons';
import { CoreNavigator } from '@services/navigator';
import { Params } from '@angular/router';
import { Subscription } from 'rxjs';
@ -47,6 +47,9 @@ import {
} from '@classes/items-management/swipe-slides-dynamic-items-manager-source';
import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker';
import { AddonCalendarEventsSource } from '@addons/calendar/classes/events-source';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreTime } from '@singletons/time';
/**
* Page that displays the calendar events for a certain day.
@ -73,6 +76,7 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy {
protected onlineObserver: Subscription;
protected filterChangedObserver: CoreEventObserver;
protected managerUnsubscribe?: () => void;
protected logView: () => void;
periodName?: string;
manager?: CoreSwipeSlidesDynamicItemsManager<PreloadedDay, AddonCalendarDaySlidesItemsManagerSource>;
@ -186,6 +190,28 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy {
this.isOnline = CoreNetwork.isOnline();
});
});
this.logView = CoreTime.once(() => {
const day = this.manager?.getSelectedItem();
if (!day) {
return;
}
const params = {
course: this.filter.courseId,
time: day.moment.unix(),
};
CoreAnalytics.logEvent({
type: CoreAnalyticsEventType.VIEW_ITEM_LIST,
ws: 'core_calendar_get_calendar_day_view',
name: Translate.instant('addon.calendar.dayviewtitle', { $a: this.periodName }),
data: {
...params,
category: 'calendar',
},
url: CoreUrlUtils.addParamsToUrl('/calendar/view.php?view=day', params),
});
});
}
/**
@ -209,7 +235,7 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy {
year: CoreNavigator.getRouteNumberParam('year'),
month: month ? month - 1 : undefined,
date: CoreNavigator.getRouteNumberParam('day'),
}));
}).startOf('day'));
this.manager = new CoreSwipeSlidesDynamicItemsManager(source);
this.managerUnsubscribe = this.manager.addListener({
onSelectedItemUpdated: (item) => {
@ -246,6 +272,8 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy {
await this.manager?.getSource().fetchData(this.filter.courseId);
await this.manager?.getSource().load(this.manager?.getSelectedItem());
this.logView();
} catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'addon.calendar.errorloadevents', true);
}
@ -500,6 +528,7 @@ class AddonCalendarDaySlidesItemsManagerSource extends CoreSwipeSlidesDynamicIte
canCreate = false;
protected dayPage: AddonCalendarDayPage;
protected sendLog = true;
constructor(page: AddonCalendarDayPage, initialMoment: moment.Moment) {
super({ moment: initialMoment });
@ -780,6 +809,7 @@ class AddonCalendarDaySlidesItemsManagerSource extends CoreSwipeSlidesDynamicIte
promises.push(AddonCalendar.invalidateTimeFormat());
this.categories = undefined; // Get categories again.
this.sendLog = true;
if (selectedDay) {
selectedDay.dirty = true;

View File

@ -27,6 +27,9 @@ import { AddonCompetencyPlanCompetenciesSource } from '@addons/competency/classe
import { AddonCompetencyCourseCompetenciesSource } from '@addons/competency/classes/competency-course-competencies-source';
import { CoreListItemsManager } from '@classes/items-management/list-items-manager';
import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
import { CoreSites } from '@services/sites';
import { CoreTime } from '@singletons/time';
/**
* Page that displays the list of competencies of a learning plan.
@ -46,8 +49,11 @@ export class AddonCompetencyCompetenciesPage implements AfterViewInit, OnDestroy
title = '';
protected logView: () => void;
constructor() {
const planId = CoreNavigator.getRouteNumberParam('planId');
this.logView = CoreTime.once(() => this.performLogView());
if (!planId) {
const courseId = CoreNavigator.getRequiredRouteNumberParam('courseId');
@ -96,6 +102,8 @@ export class AddonCompetencyCompetenciesPage implements AfterViewInit, OnDestroy
} else {
this.title = Translate.instant('addon.competency.coursecompetencies');
}
this.logView();
} catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'Error getting competencies data.');
}
@ -122,4 +130,42 @@ export class AddonCompetencyCompetenciesPage implements AfterViewInit, OnDestroy
this.competencies.destroy();
}
/**
* Log view.
*/
protected performLogView(): void {
const source = this.competencies.getSource();
if (source instanceof AddonCompetencyPlanCompetenciesSource) {
CoreAnalytics.logEvent({
type: CoreAnalyticsEventType.VIEW_ITEM_LIST,
ws: 'tool_lp_data_for_plan_page',
name: this.title,
data: {
category: 'competency',
planid: source.PLAN_ID,
},
url: `/admin/tool/lp/plan.php?id=${source.PLAN_ID}`,
});
return;
}
if (source.USER_ID && source.USER_ID !== CoreSites.getCurrentSiteUserId()) {
// Only log event when viewing own competencies. In LMS viewing students competencies uses a different view.
return;
}
CoreAnalytics.logEvent({
type: CoreAnalyticsEventType.VIEW_ITEM_LIST,
ws: 'tool_lp_data_for_course_competencies_page',
name: this.title,
data: {
category: 'competency',
courseid: source.COURSE_ID,
},
url: `/admin/tool/lp/coursecompetencies.php?courseid=${source.COURSE_ID}`,
});
}
}

View File

@ -27,6 +27,7 @@ import {
AddonCompetency,
AddonCompetencyDataForPlanPageCompetency,
AddonCompetencyDataForCourseCompetenciesPageCompetency,
AddonCompetencyProvider,
} from '@addons/competency/services/competency';
import { CoreNavigator } from '@services/navigator';
import { IonRefresher } from '@ionic/angular';
@ -38,6 +39,9 @@ import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/
import { AddonCompetencyPlanCompetenciesSource } from '@addons/competency/classes/competency-plan-competencies-source';
import { ActivatedRouteSnapshot } from '@angular/router';
import { AddonCompetencyCourseCompetenciesSource } from '@addons/competency/classes/competency-course-competencies-source';
import { CoreTime } from '@singletons/time';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
import { CoreUrlUtils } from '@services/utils/url';
/**
* Page that displays the competency information.
@ -58,9 +62,11 @@ export class AddonCompetencyCompetencyPage implements OnInit, OnDestroy {
contextLevel?: string;
contextInstanceId?: number;
protected fetchSuccess = false;
protected logView: () => void;
constructor() {
this.logView = CoreTime.once(() => this.performLogView());
try {
const planId = CoreNavigator.getRouteNumberParam('planId');
@ -156,31 +162,7 @@ export class AddonCompetencyCompetencyPage implements OnInit, OnDestroy {
}
});
if (!this.fetchSuccess) {
this.fetchSuccess = true;
const name = this.competency.competency.competency.shortname;
if (source instanceof AddonCompetencyPlanCompetenciesSource) {
this.planStatus && await CoreUtils.ignoreErrors(
AddonCompetency.logCompetencyInPlanView(
source.PLAN_ID,
this.requireCompetencyId(),
this.planStatus,
name,
source.user?.id,
),
);
} else {
await CoreUtils.ignoreErrors(
AddonCompetency.logCompetencyInCourseView(
source.COURSE_ID,
this.requireCompetencyId(),
name,
source.USER_ID,
),
);
}
}
this.logView();
} catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'Error getting competency data.');
}
@ -288,6 +270,73 @@ export class AddonCompetencyCompetencyPage implements OnInit, OnDestroy {
return competency.usercompetencysummary;
}
/**
* Log view.
*/
protected async performLogView(): Promise<void> {
if (!this.competency) {
return;
}
const source = this.competencies.getSource();
const compId = this.requireCompetencyId();
const name = this.competency.competency.competency.shortname;
const userId = source.user?.id;
if (source instanceof AddonCompetencyPlanCompetenciesSource) {
if (!this.planStatus) {
return;
}
await CoreUtils.ignoreErrors(
AddonCompetency.logCompetencyInPlanView(source.PLAN_ID, compId, this.planStatus, name, userId),
);
const wsName = this.planStatus === AddonCompetencyProvider.STATUS_COMPLETE
? 'core_competency_user_competency_plan_viewed'
: 'core_competency_user_competency_viewed_in_plan';
CoreAnalytics.logEvent({
type: CoreAnalyticsEventType.VIEW_ITEM,
ws: wsName,
name,
data: {
id: compId,
category: 'competency',
planid: source.PLAN_ID,
planstatus: this.planStatus,
userid: userId,
},
url: CoreUrlUtils.addParamsToUrl('/admin/tool/lp/user_competency_in_plan.php', {
planid: source.PLAN_ID,
userid: userId,
competencyid: compId,
}),
});
return;
}
await CoreUtils.ignoreErrors(AddonCompetency.logCompetencyInCourseView(source.COURSE_ID, compId, name, source.USER_ID));
CoreAnalytics.logEvent({
type: CoreAnalyticsEventType.VIEW_ITEM,
ws: 'core_competency_user_competency_viewed_in_course',
name,
data: {
id: compId,
category: 'competency',
courseid: source.COURSE_ID,
userid: userId,
},
url: CoreUrlUtils.addParamsToUrl('/admin/tool/lp/user_competency_in_course.php', {
courseid: source.COURSE_ID,
competencyid: compId,
userid: userId,
}),
});
}
}
/**

View File

@ -20,6 +20,8 @@ import { CoreNavigator } from '@services/navigator';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreUtils } from '@services/utils/utils';
import { ADDON_COMPETENCY_SUMMARY_PAGE } from '@addons/competency/competency.module';
import { CoreTime } from '@singletons/time';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
/**
* Page that displays the competency summary.
@ -36,7 +38,30 @@ export class AddonCompetencyCompetencySummaryPage implements OnInit {
contextLevel?: ContextLevel;
contextInstanceId?: number;
protected fetchSuccess = false; // Whether a fetch was finished successfully.
protected logView: () => void;
constructor() {
this.logView = CoreTime.once(async () => {
if (!this.competency) {
return;
}
await CoreUtils.ignoreErrors(
AddonCompetency.logCompetencyView(this.competencyId, this.competency.competency.shortname),
);
CoreAnalytics.logEvent({
type: CoreAnalyticsEventType.VIEW_ITEM_LIST,
ws: 'core_competency_competency_viewed',
name: this.competency.competency.shortname,
data: {
competencyId: this.competencyId,
category: 'competency',
},
url: `/admin/tool/lp/user_competency.php?id=${this.competencyId}`,
});
});
}
/**
* @inheritdoc
@ -77,10 +102,7 @@ export class AddonCompetencyCompetencySummaryPage implements OnInit {
this.competency = result.competency;
if (!this.fetchSuccess) {
this.fetchSuccess = true;
CoreUtils.ignoreErrors(AddonCompetency.logCompetencyView(this.competencyId, this.competency.competency.shortname));
}
this.logView();
} catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'Error getting competency summary data.');
}

View File

@ -26,6 +26,10 @@ import { ADDON_COMPETENCY_SUMMARY_PAGE } from '@addons/competency/competency.mod
import { CoreListItemsManager } from '@classes/items-management/list-items-manager';
import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker';
import { AddonCompetencyCourseCompetenciesSource } from '@addons/competency/classes/competency-course-competencies-source';
import { CoreTime } from '@singletons/time';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
import { CoreSites } from '@services/sites';
import { Translate } from '@singletons';
/**
* Page that displays the list of competencies of a course.
@ -41,7 +45,11 @@ export class AddonCompetencyCourseCompetenciesPage implements OnInit, OnDestroy
AddonCompetencyCourseCompetenciesSource
>;
protected logView: () => void;
constructor() {
this.logView = CoreTime.once(() => this.performLogView());
try {
const courseId = CoreNavigator.getRequiredRouteNumberParam('courseId');
const userId = CoreNavigator.getRouteNumberParam('userId');
@ -53,7 +61,6 @@ export class AddonCompetencyCourseCompetenciesPage implements OnInit, OnDestroy
this.competencies = new CoreListItemsManager(source, AddonCompetencyCourseCompetenciesPage);
} catch (error) {
CoreDomUtils.showErrorModal(error);
CoreNavigator.back();
return;
@ -112,6 +119,8 @@ export class AddonCompetencyCourseCompetenciesPage implements OnInit, OnDestroy
protected async fetchCourseCompetencies(): Promise<void> {
try {
await this.competencies.getSource().reload();
this.logView();
} catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'Error getting course competencies data.');
}
@ -147,4 +156,26 @@ export class AddonCompetencyCourseCompetenciesPage implements OnInit, OnDestroy
});
}
/**
* Log view.
*/
protected performLogView(): void {
const source = this.competencies.getSource();
if (source.USER_ID && source.USER_ID !== CoreSites.getCurrentSiteUserId()) {
// Only log event when viewing own competencies. In LMS viewing students competencies uses a different view.
return;
}
CoreAnalytics.logEvent({
type: CoreAnalyticsEventType.VIEW_ITEM_LIST,
ws: 'tool_lp_data_for_course_competencies_page',
name: Translate.instant('addon.competency.coursecompetencies'),
data: {
category: 'competency',
courseid: source.COURSE_ID,
},
url: `/admin/tool/lp/coursecompetencies.php?courseid=${source.COURSE_ID}`,
});
}
}

View File

@ -23,6 +23,8 @@ import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/
import { AddonCompetencyPlansSource } from '@addons/competency/classes/competency-plans-source';
import { CoreListItemsManager } from '@classes/items-management/list-items-manager';
import { AddonCompetencyPlanCompetenciesSource } from '@addons/competency/classes/competency-plan-competencies-source';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
import { CoreTime } from '@singletons/time';
/**
* Page that displays a learning plan.
@ -36,7 +38,11 @@ export class AddonCompetencyPlanPage implements OnInit, OnDestroy {
plans!: CoreSwipeNavigationItemsManager;
competencies!: CoreListItemsManager<AddonCompetencyDataForPlanPageCompetency, AddonCompetencyPlanCompetenciesSource>;
protected logView: () => void;
constructor() {
this.logView = CoreTime.once(() => this.performLogView());
try {
const planId = CoreNavigator.getRequiredRouteNumberParam('planId');
const userId = CoreNavigator.getRouteNumberParam('userId');
@ -93,6 +99,8 @@ export class AddonCompetencyPlanPage implements OnInit, OnDestroy {
protected async fetchLearningPlan(): Promise<void> {
try {
await this.competencies.getSource().reload();
this.logView();
} catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'Error getting learning plan data.');
}
@ -111,4 +119,26 @@ export class AddonCompetencyPlanPage implements OnInit, OnDestroy {
});
}
/**
* Log view.
*/
protected performLogView(): void {
if (!this.plan) {
return;
}
const planId = this.competencies.getSource().PLAN_ID;
CoreAnalytics.logEvent({
type: CoreAnalyticsEventType.VIEW_ITEM_LIST,
ws: 'tool_lp_data_for_plan_page',
name: this.plan.plan.name,
data: {
category: 'competency',
planid: planId,
},
url: `/admin/tool/lp/coursecompetencies.php?id=${planId}`,
});
}
}

View File

@ -20,6 +20,10 @@ import { CoreNavigator } from '@services/navigator';
import { AddonCompetencyPlanFormatted, AddonCompetencyPlansSource } from '@addons/competency/classes/competency-plans-source';
import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker';
import { CoreListItemsManager } from '@classes/items-management/list-items-manager';
import { CoreTime } from '@singletons/time';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
import { CoreSites } from '@services/sites';
import { Translate } from '@singletons';
/**
* Page that displays the list of learning plans.
@ -34,11 +38,25 @@ export class AddonCompetencyPlanListPage implements AfterViewInit, OnDestroy {
plans: CoreListItemsManager<AddonCompetencyPlanFormatted, AddonCompetencyPlansSource>;
protected logView: () => void;
constructor() {
const userId = CoreNavigator.getRouteNumberParam('userId');
const source = CoreRoutedItemsManagerSourcesTracker.getOrCreateSource(AddonCompetencyPlansSource, [userId]);
this.plans = new CoreListItemsManager(source, AddonCompetencyPlanListPage);
this.logView = CoreTime.once(async () => {
const userId = source.USER_ID ?? CoreSites.getCurrentSiteId();
CoreAnalytics.logEvent({
type: CoreAnalyticsEventType.VIEW_ITEM_LIST,
ws: 'tool_lp_data_for_plans_page',
name: Translate.instant('addon.competency.userplans'),
data: { userid: userId },
url: `/admin/tool/lp/plans.php?userid=${userId}`,
});
});
}
/**
@ -58,6 +76,8 @@ export class AddonCompetencyPlanListPage implements AfterViewInit, OnDestroy {
protected async fetchLearningPlans(): Promise<void> {
try {
await this.plans.load();
this.logView();
} catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'Error getting learning plans data.');
}

View File

@ -16,7 +16,6 @@ import { Injectable } from '@angular/core';
import { CoreSite, CoreSiteWSPreSets } from '@classes/site';
import { CoreCommentsArea } from '@features/comments/services/comments';
import { CoreCourseSummary, CoreCourseModuleSummary } from '@features/course/services/course';
import { CorePushNotifications } from '@features/pushnotifications/services/pushnotifications';
import { CoreUserSummary } from '@features/user/services/user';
import { CoreSites } from '@services/sites';
import { CoreUtils } from '@services/utils/utils';
@ -495,7 +494,7 @@ export class AddonCompetencyProvider {
* @param planId ID of the plan.
* @param competencyId ID of the competency.
* @param planStatus Current plan Status to decide what action should be logged.
* @param name Name of the competency.
* @param name Deprecated, not used anymore.
* @param userId User ID. If not defined, current user.
* @param siteId Site ID. If not defined, current site.
* @returns Promise resolved when the WS call is successful.
@ -525,12 +524,6 @@ export class AddonCompetencyProvider {
? 'core_competency_user_competency_plan_viewed'
: 'core_competency_user_competency_viewed_in_plan';
CorePushNotifications.logViewEvent(competencyId, name, 'competency', wsName, {
planid: planId,
planstatus: planStatus,
userid: userId,
}, siteId);
await site.write(wsName, params, preSets);
}
@ -539,7 +532,7 @@ export class AddonCompetencyProvider {
*
* @param courseId ID of the course.
* @param competencyId ID of the competency.
* @param name Name of the competency.
* @param name Deprecated, not used anymore.
* @param userId User ID. If not defined, current user.
* @param siteId Site ID. If not defined, current site.
* @returns Promise resolved when the WS call is successful.
@ -564,14 +557,7 @@ export class AddonCompetencyProvider {
typeExpected: 'boolean',
};
const wsName = 'core_competency_user_competency_viewed_in_course';
CorePushNotifications.logViewEvent(competencyId, name, 'competency', 'wsName', {
courseid: courseId,
userid: userId,
}, siteId);
await site.write(wsName, params, preSets);
await site.write('core_competency_user_competency_viewed_in_course', params, preSets);
}
/**
@ -593,10 +579,7 @@ export class AddonCompetencyProvider {
typeExpected: 'boolean',
};
const wsName = 'core_competency_competency_viewed';
CorePushNotifications.logViewEvent(competencyId, name, 'competency', wsName, {}, siteId);
await site.write(wsName, params, preSets);
await site.write('core_competency_competency_viewed', params, preSets);
}
}

View File

@ -19,9 +19,12 @@ import {
import { Component, OnInit } from '@angular/core';
import { CoreUser, CoreUserProfile } from '@features/user/services/user';
import { IonRefresher } from '@ionic/angular';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
import { CoreNavigator } from '@services/navigator';
import { CoreSites } from '@services/sites';
import { CoreDomUtils } from '@services/utils/dom';
import { Translate } from '@singletons';
import { CoreTime } from '@singletons/time';
/**
* Page that displays the course completion report.
@ -33,6 +36,7 @@ import { CoreDomUtils } from '@services/utils/dom';
export class AddonCourseCompletionReportPage implements OnInit {
protected userId!: number;
protected logView: () => void;
courseId!: number;
completionLoaded = false;
@ -42,6 +46,21 @@ export class AddonCourseCompletionReportPage implements OnInit {
statusText?: string;
user?: CoreUserProfile;
constructor() {
this.logView = CoreTime.once(() => {
CoreAnalytics.logEvent({
type: CoreAnalyticsEventType.VIEW_ITEM,
ws: 'core_completion_get_course_completion_status',
name: Translate.instant('addon.coursecompletion.coursecompletion'),
data: {
course: this.courseId,
user: this.userId,
},
url: `/blocks/completionstatus/details.php?course=${this.courseId}&user=${this.userId}`,
});
});
}
/**
* @inheritdoc
*/
@ -77,6 +96,7 @@ export class AddonCourseCompletionReportPage implements OnInit {
this.showSelfComplete = AddonCourseCompletion.canMarkSelfCompleted(this.userId, this.completion);
this.tracked = true;
this.logView();
} catch (error) {
if (error && error.errorcode == 'notenroled') {
// Not enrolled error, probably a teacher.

View File

@ -57,7 +57,7 @@ export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityCo
@ViewChild(AddonModAssignSubmissionComponent) submissionComponent?: AddonModAssignSubmissionComponent;
component = AddonModAssignProvider.COMPONENT;
moduleName = 'assign';
pluginName = 'assign';
assign?: AddonModAssignAssign; // The assign object.
canViewAllSubmissions = false; // Whether the user can view all submissions.
@ -230,14 +230,20 @@ export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityCo
return; // Shouldn't happen.
}
await AddonModAssign.logView(this.assign.id, this.assign.name);
await CoreUtils.ignoreErrors(AddonModAssign.logView(this.assign.id));
this.analyticsLogEvent('mod_assign_view_assign');
if (this.canViewAllSubmissions) {
// User can see all submissions, log grading view.
CoreUtils.ignoreErrors(AddonModAssign.logGradingView(this.assign.id, this.assign.name));
await CoreUtils.ignoreErrors(AddonModAssign.logGradingView(this.assign.id));
this.analyticsLogEvent('mod_assign_view_grading_table', { sendUrl: false });
} else if (this.canViewOwnSubmission) {
// User can only see their own submission, log view the user submission.
CoreUtils.ignoreErrors(AddonModAssign.logSubmissionView(this.assign.id, this.assign.name));
await CoreUtils.ignoreErrors(AddonModAssign.logSubmissionView(this.assign.id));
this.analyticsLogEvent('mod_assign_view_submission_status', { sendUrl: false });
}
}

View File

@ -45,6 +45,7 @@
"gradelocked": "This grade is locked or overridden in the gradebook.",
"gradenotsynced": "Grade not synced",
"gradeoutof": "Grade out of {{$a}}",
"grading": "Grading",
"gradingstatus": "Grading status",
"groupsubmissionsettings": "Group submission settings",
"hiddenuser": "Participant",
@ -101,6 +102,7 @@
"submittedlate": "Assignment was submitted {{$a}} late",
"submittedovertime": "Assignment was submitted {{$a}} over the time limit",
"submittedundertime": "Assignment was submitted {{$a}} under the time limit",
"subpagetitle": "{{$a.contextname}} - {{$a.subpage}}",
"syncblockedusercomponent": "user grade",
"timelimit": "Time limit",
"timemodified": "Last modified",

View File

@ -39,6 +39,7 @@ import { AddonModAssignOffline } from '../../services/assign-offline';
import { AddonModAssignSync } from '../../services/assign-sync';
import { CoreUtils } from '@services/utils/utils';
import { CoreWSExternalFile } from '@services/ws';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
/**
* Page that allows adding or editing an assigment submission.
@ -226,6 +227,17 @@ export class AddonModAssignEditPage implements OnInit, OnDestroy, CanLeave {
// No offline data found.
this.hasOffline = false;
}
CoreAnalytics.logEvent({
type: CoreAnalyticsEventType.VIEW_ITEM,
ws: 'mod_assign_save_submission',
name: Translate.instant('addon.mod_assign.subpagetitle', {
contextname: this.assign.name,
subpage: Translate.instant('addon.mod_assign.editsubmission'),
}),
data: { id: this.assign.id, category: 'assign' },
url: `/mod/assign/view.php?action=editsubmission&id=${this.moduleId}`,
});
} catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'Error getting assigment data.');

View File

@ -34,6 +34,7 @@ import {
AddonModAssignManualSyncData,
AddonModAssignAutoSyncData,
} from '../../services/assign-sync';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
/**
* Page that displays a list of submissions of an assignment.
@ -168,6 +169,21 @@ export class AddonModAssignSubmissionListPage implements AfterViewInit, OnDestro
protected async fetchAssignment(sync = false): Promise<void> {
try {
await this.submissions.getSource().loadAssignment(sync);
if (!this.assign) {
return;
}
CoreAnalytics.logEvent({
type: CoreAnalyticsEventType.VIEW_ITEM_LIST,
ws: 'mod_assign_get_submissions',
name: Translate.instant('addon.mod_assign.subpagetitle', {
contextname: this.assign.name,
subpage: Translate.instant('addon.mod_assign.grading'),
}),
data: { assignid: this.assign.id, category: 'assign' },
url: `/mod/assign/view.php?id=${this.assign.cmid}&action=grading`,
});
} catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'Error getting assigment data.');
}

View File

@ -25,6 +25,9 @@ import { CoreDomUtils } from '@services/utils/dom';
import { AddonModAssignListFilterName, AddonModAssignSubmissionsSource } from '../../classes/submissions-source';
import { AddonModAssignSubmissionComponent } from '../../components/submission/submission';
import { AddonModAssign, AddonModAssignAssign } from '../../services/assign';
import { CoreTime } from '@singletons/time';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
import { Translate } from '@singletons';
/**
* Page that displays a submission.
@ -49,8 +52,29 @@ export class AddonModAssignSubmissionReviewPage implements OnInit, OnDestroy, Ca
protected assign?: AddonModAssignAssign; // The assignment the submission belongs to.
protected blindMarking = false; // Whether it uses blind marking.
protected forceLeave = false; // To allow leaving the page without checking for changes.
protected logView: () => void;
constructor(protected route: ActivatedRoute) { }
constructor(protected route: ActivatedRoute) {
this.logView = CoreTime.once(() => {
if (!this.assign) {
return;
}
const id = this.blindMarking ? this.blindId : this.submitId;
const paramName = this.blindMarking ? 'blindid' : 'userid';
CoreAnalytics.logEvent({
type: CoreAnalyticsEventType.VIEW_ITEM,
ws: 'mod_assign_get_submission_status',
name: Translate.instant('addon.mod_assign.subpagetitle', {
contextname: this.assign.name,
subpage: Translate.instant('addon.mod_assign.grading'),
}),
data: { id, assignid: this.assign.id, category: 'assign' },
url: `/mod/assign/view.php?id=${this.assign.cmid}&action=grader&${paramName}=${id}`,
});
});
}
/**
* @inheritdoc
@ -84,6 +108,7 @@ export class AddonModAssignSubmissionReviewPage implements OnInit, OnDestroy, Ca
}
this.fetchSubmission().finally(() => {
this.logView();
this.loaded = true;
});
});

View File

@ -878,23 +878,19 @@ export class AddonModAssignProvider {
* Report an assignment submission as being viewed.
*
* @param assignid Assignment ID.
* @param name Name of the assign.
* @param siteId Site ID. If not defined, current site.
* @returns Promise resolved when the WS call is successful.
*/
async logSubmissionView(assignid: number, name?: string, siteId?: string): Promise<void> {
async logSubmissionView(assignid: number, siteId?: string): Promise<void> {
const params: AddonModAssignViewSubmissionStatusWSParams = {
assignid,
};
await CoreCourseLogHelper.logSingle(
await CoreCourseLogHelper.log(
'mod_assign_view_submission_status',
params,
AddonModAssignProvider.COMPONENT,
assignid,
name,
'assign',
{},
siteId,
);
}
@ -903,23 +899,19 @@ export class AddonModAssignProvider {
* Report an assignment grading table is being viewed.
*
* @param assignid Assignment ID.
* @param name Name of the assign.
* @param siteId Site ID. If not defined, current site.
* @returns Promise resolved when the WS call is successful.
*/
async logGradingView(assignid: number, name?: string, siteId?: string): Promise<void> {
async logGradingView(assignid: number, siteId?: string): Promise<void> {
const params: AddonModAssignViewGradingTableWSParams = {
assignid,
};
await CoreCourseLogHelper.logSingle(
await CoreCourseLogHelper.log(
'mod_assign_view_grading_table',
params,
AddonModAssignProvider.COMPONENT,
assignid,
name,
'assign',
{},
siteId,
);
}
@ -928,23 +920,19 @@ export class AddonModAssignProvider {
* Report an assign as being viewed.
*
* @param assignid Assignment ID.
* @param name Name of the assign.
* @param siteId Site ID. If not defined, current site.
* @returns Promise resolved when the WS call is successful.
*/
async logView(assignid: number, name?: string, siteId?: string): Promise<void> {
async logView(assignid: number, siteId?: string): Promise<void> {
const params: AddonModAssignViewAssignWSParams = {
assignid,
};
await CoreCourseLogHelper.logSingle(
await CoreCourseLogHelper.log(
'mod_assign_view_assign',
params,
AddonModAssignProvider.COMPONENT,
assignid,
name,
'assign',
{},
siteId,
);
}

View File

@ -44,7 +44,7 @@ import {
export class AddonModBBBIndexComponent extends CoreCourseModuleMainActivityComponent implements OnInit {
component = AddonModBBBService.COMPONENT;
moduleName = 'bigbluebuttonbn';
pluginName = 'bigbluebuttonbn';
bbb?: AddonModBBBData;
groupInfo?: CoreGroupInfo;
groupId = 0;
@ -226,7 +226,9 @@ export class AddonModBBBIndexComponent extends CoreCourseModuleMainActivityCompo
return; // Shouldn't happen.
}
await AddonModBBB.logView(this.bbb.id, this.bbb.name);
await CoreUtils.ignoreErrors(AddonModBBB.logView(this.bbb.id));
this.analyticsLogEvent('mod_bigbluebuttonbn_view_bigbluebuttonbn');
}
/**

View File

@ -276,23 +276,19 @@ export class AddonModBBBService {
* Report a BBB as being viewed.
*
* @param id BBB instance ID.
* @param name Name of the BBB.
* @param siteId Site ID. If not defined, current site.
* @returns Promise resolved when the WS call is successful.
*/
async logView(id: number, name?: string, siteId?: string): Promise<void> {
async logView(id: number, siteId?: string): Promise<void> {
const params: AddonModBBBViewBigBlueButtonBNWSParams = {
bigbluebuttonbnid: id,
};
await CoreCourseLogHelper.logSingle(
await CoreCourseLogHelper.log(
'mod_bigbluebuttonbn_view_bigbluebuttonbn',
params,
AddonModBBBService.COMPONENT,
id,
name,
'bigbluebuttonbn',
{},
siteId,
);
}

View File

@ -19,6 +19,7 @@ import { CoreCourseContentsPage } from '@features/course/pages/contents/contents
import { CoreCourse } from '@features/course/services/course';
import { CoreNavigator } from '@services/navigator';
import { AddonModBookModuleHandlerService } from '../../services/handlers/module';
import { CoreUtils } from '@services/utils/utils';
/**
* Component that displays a book entry page.
@ -29,6 +30,7 @@ import { AddonModBookModuleHandlerService } from '../../services/handlers/module
})
export class AddonModBookIndexComponent extends CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy {
pluginName = 'book';
showNumbers = true;
addPadding = true;
showBullets = false;
@ -102,7 +104,9 @@ export class AddonModBookIndexComponent extends CoreCourseModuleMainResourceComp
* @inheritdoc
*/
protected async logActivity(): Promise<void> {
AddonModBook.logView(this.module.instance, undefined, this.module.name);
await CoreUtils.ignoreErrors(AddonModBook.logView(this.module.instance));
this.analyticsLogEvent('mod_book_view_book');
}
/**

View File

@ -40,6 +40,8 @@ import {
AddonModBookProvider,
AddonModBookTocChapter,
} from '../../services/book';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
import { CoreUrlUtils } from '@services/utils/url';
/**
* Page that displays a book contents.
@ -286,7 +288,15 @@ export class AddonModBookContentsPage implements OnInit, OnDestroy {
}
// Chapter loaded, log view.
await CoreUtils.ignoreErrors(AddonModBook.logView(this.module.instance, chapterId, this.module.name));
await CoreUtils.ignoreErrors(AddonModBook.logView(this.module.instance, chapterId));
CoreAnalytics.logEvent({
type: CoreAnalyticsEventType.VIEW_ITEM,
ws: 'mod_book_view_book',
name: this.module.name,
data: { id: this.module.instance, category: 'book', chapterid: chapterId },
url: CoreUrlUtils.addParamsToUrl(`/mod/book/view.php?id=${this.module.id}`, { chapterid: chapterId }),
});
const currentChapterIndex = this.chapters.findIndex((chapter) => chapter.id == chapterId);
const isLastChapter = currentChapterIndex < 0 || this.chapters[currentChapterIndex + 1] === undefined;

View File

@ -359,24 +359,20 @@ export class AddonModBookProvider {
*
* @param id Module ID.
* @param chapterId Chapter ID.
* @param name Name of the book.
* @param siteId Site ID. If not defined, current site.
* @returns Promise resolved when the WS call is successful.
*/
async logView(id: number, chapterId?: number, name?: string, siteId?: string): Promise<void> {
async logView(id: number, chapterId?: number, siteId?: string): Promise<void> {
const params: AddonModBookViewBookWSParams = {
bookid: id,
chapterid: chapterId,
};
await CoreCourseLogHelper.logSingle(
await CoreCourseLogHelper.log(
'mod_book_view_book',
params,
AddonModBookProvider.COMPONENT,
id,
name,
'book',
{ chapterid: chapterId },
siteId,
);
}

View File

@ -32,7 +32,7 @@ import { AddonModChatModuleHandlerService } from '../../services/handlers/module
export class AddonModChatIndexComponent extends CoreCourseModuleMainActivityComponent implements OnInit {
component = AddonModChatProvider.COMPONENT;
moduleName = 'chat';
pluginName = 'chat';
chat?: AddonModChatChat;
chatInfo?: {
date: string;
@ -85,7 +85,9 @@ export class AddonModChatIndexComponent extends CoreCourseModuleMainActivityComp
return; // Shouldn't happen.
}
await AddonModChat.logView(this.chat.id, this.chat.name);
await AddonModChat.logView(this.chat.id);
this.analyticsLogEvent('mod_chat_view_chat');
}
/**

View File

@ -28,6 +28,8 @@ import { Subscription } from 'rxjs';
import { AddonModChatUsersModalComponent, AddonModChatUsersModalResult } from '../../components/users-modal/users-modal';
import { AddonModChat, AddonModChatProvider, AddonModChatUser } from '../../services/chat';
import { AddonModChatFormattedMessage, AddonModChatHelper } from '../../services/chat-helper';
import { CoreTime } from '@singletons/time';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
/**
* Page that displays a chat session.
@ -61,6 +63,7 @@ export class AddonModChatChatPage implements OnInit, OnDestroy, CanLeave {
protected viewDestroyed = false;
protected pollingRunning = false;
protected users: AddonModChatUser[] = [];
protected logView: () => void;
constructor() {
this.currentUserId = CoreSites.getCurrentSiteUserId();
@ -71,6 +74,16 @@ export class AddonModChatChatPage implements OnInit, OnDestroy, CanLeave {
this.isOnline = CoreNetwork.isOnline();
});
});
this.logView = CoreTime.once(() => {
CoreAnalytics.logEvent({
type: CoreAnalyticsEventType.VIEW_ITEM_LIST,
ws: 'mod_chat_get_chat_latest_messages',
name: this.title,
data: { chatid: this.chatId, category: 'chat' },
url: `/mod/chat/gui_ajax/index.php?id=${this.chatId}`,
});
});
}
/**
@ -88,6 +101,7 @@ export class AddonModChatChatPage implements OnInit, OnDestroy, CanLeave {
await this.fetchMessages();
this.startPolling();
this.logView();
} catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'addon.mod_chat.errorwhileconnecting', true);
CoreNavigator.back();

View File

@ -21,6 +21,9 @@ import { CoreDomUtils } from '@services/utils/dom';
import { CoreUtils } from '@services/utils/utils';
import { AddonModChat } from '../../services/chat';
import { AddonModChatFormattedSessionMessage, AddonModChatHelper } from '../../services/chat-helper';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
import { Translate } from '@singletons';
import { CoreTime } from '@singletons/time';
/**
* Page that displays list of chat session messages.
@ -42,6 +45,19 @@ export class AddonModChatSessionMessagesPage implements OnInit {
protected sessionStart!: number;
protected sessionEnd!: number;
protected groupId!: number;
protected logView: () => void;
constructor() {
this.logView = CoreTime.once(() => {
CoreAnalytics.logEvent({
type: CoreAnalyticsEventType.VIEW_ITEM_LIST,
ws: 'mod_chat_view_sessions',
name: Translate.instant('addon.mod_chat.messages'),
data: { chatid: this.chatId, category: 'chat' },
url: `/mod/chat/report.php?id=${this.cmId}&start=${this.sessionStart}&end=${this.sessionEnd}`,
});
});
}
/**
* @inheritdoc

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { AfterViewInit, Component, OnDestroy, ViewChild } from '@angular/core';
import { AfterViewInit, Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { CoreListItemsManager } from '@classes/items-management/list-items-manager';
import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker';
import { CoreSplitViewComponent } from '@components/split-view/split-view';
@ -21,6 +21,9 @@ import { CoreGroupInfo } from '@services/groups';
import { CoreNavigator } from '@services/navigator';
import { CoreDomUtils } from '@services/utils/dom';
import { AddonModChatSessionFormatted, AddonModChatSessionsSource } from '../../classes/chat-sessions-source';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
import { CoreTime } from '@singletons/time';
import { Translate } from '@singletons';
/**
* Page that displays list of chat sessions.
@ -29,14 +32,32 @@ import { AddonModChatSessionFormatted, AddonModChatSessionsSource } from '../../
selector: 'page-addon-mod-chat-sessions',
templateUrl: 'sessions.html',
})
export class AddonModChatSessionsPage implements AfterViewInit, OnDestroy {
export class AddonModChatSessionsPage implements OnInit, AfterViewInit, OnDestroy {
@ViewChild(CoreSplitViewComponent) splitView!: CoreSplitViewComponent;
sessions!: CoreListItemsManager<AddonModChatSessionFormatted, AddonModChatSessionsSource>;
courseId?: number;
protected logView: () => void;
constructor() {
this.logView = CoreTime.once(() => {
const source = this.sessions.getSource();
CoreAnalytics.logEvent({
type: CoreAnalyticsEventType.VIEW_ITEM_LIST,
ws: 'mod_chat_view_sessions',
name: Translate.instant('addon.mod_chat.chatreport'),
data: { chatid: source.CHAT_ID, category: 'chat' },
url: `/mod/chat/report.php?id=${source.CM_ID}`,
});
});
}
/**
* @inheritdoc
*/
ngOnInit(): void {
try {
this.courseId = CoreNavigator.getRequiredRouteNumberParam('courseId');
const chatId = CoreNavigator.getRequiredRouteNumberParam('chatId');
@ -91,6 +112,8 @@ export class AddonModChatSessionsPage implements AfterViewInit, OnDestroy {
async fetchSessions(): Promise<void> {
try {
await this.sessions.load();
this.logView();
} catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'core.errorloadingcontent', true);
}

View File

@ -87,23 +87,19 @@ export class AddonModChatProvider {
* Report a chat as being viewed.
*
* @param id Chat instance ID.
* @param name Name of the chat.
* @param siteId Site ID. If not defined, current site.
* @returns Promise resolved when the WS call is successful.
*/
async logView(id: number, name?: string, siteId?: string): Promise<void> {
async logView(id: number, siteId?: string): Promise<void> {
const params: AddonModChatViewChatWSParams = {
chatid: id,
};
await CoreCourseLogHelper.logSingle(
await CoreCourseLogHelper.log(
'mod_chat_view_chat',
params,
AddonModChatProvider.COMPONENT,
id,
name,
'chat',
{},
siteId,
);
}

View File

@ -48,7 +48,7 @@ import { AddonModChoicePrefetchHandler } from '../../services/handlers/prefetch'
export class AddonModChoiceIndexComponent extends CoreCourseModuleMainActivityComponent implements OnInit {
component = AddonModChoiceProvider.COMPONENT;
moduleName = 'choice';
pluginName = 'choice';
choice?: AddonModChoiceChoice;
options: AddonModChoiceOption[] = [];
@ -321,7 +321,9 @@ export class AddonModChoiceIndexComponent extends CoreCourseModuleMainActivityCo
return; // Shouldn't happen.
}
await AddonModChoice.logView(this.choice.id, this.choice.name);
await AddonModChoice.logView(this.choice.id);
this.analyticsLogEvent('mod_choice_view_choice');
}
/**
@ -386,6 +388,8 @@ export class AddonModChoiceIndexComponent extends CoreCourseModuleMainActivityCo
this.checkCompletion();
}
this.analyticsLogEvent('mod_choice_view_choice', { data: { notify: 'choicesaved' } });
await this.dataUpdated(online);
} catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'addon.mod_choice.cannotsubmit', true);
@ -412,6 +416,8 @@ export class AddonModChoiceIndexComponent extends CoreCourseModuleMainActivityCo
this.content?.scrollToTop();
this.analyticsLogEvent('mod_choice_view_choice', { data: { action: 'delchoice' } });
// Refresh the data. Don't call dataUpdated because deleting an answer doesn't mark the choice as outdated.
await this.refreshContent(false);
} catch (error) {

View File

@ -365,23 +365,19 @@ export class AddonModChoiceProvider {
* Report the choice as being viewed.
*
* @param id Choice ID.
* @param name Name of the choice.
* @param siteId Site ID. If not defined, current site.
* @returns Promise resolved when the WS call is successful.
*/
logView(id: number, name?: string, siteId?: string): Promise<void> {
logView(id: number, siteId?: string): Promise<void> {
const params: AddonModChoiceViewChoiceWSParams = {
choiceid: id,
};
return CoreCourseLogHelper.logSingle(
return CoreCourseLogHelper.log(
'mod_choice_view_choice',
params,
AddonModChoiceProvider.COMPONENT,
id,
name,
'choice',
{},
siteId,
);
}

View File

@ -59,7 +59,7 @@ const contentToken = '<!-- CORE-DATABASE-CONTENT-GOES-HERE -->';
export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComponent implements OnInit, OnDestroy {
component = AddonModDataProvider.COMPONENT;
moduleName = 'data';
pluginName = 'data';
access?: AddonModDataGetDataAccessInformationWSResponse;
database?: AddonModDataData;
@ -420,8 +420,6 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp
try {
await this.fetchEntriesData();
// Log activity view for coherence with Moodle web.
await this.logActivity();
} catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'core.course.errorgetmodule', true);
} finally {
@ -470,9 +468,6 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp
try {
await this.fetchEntriesData();
// Log activity view for coherence with Moodle web.
return this.logActivity();
} catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'core.course.errorgetmodule', true);
}
@ -535,7 +530,9 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp
return;
}
await AddonModData.logView(this.database.id, this.database.name);
await AddonModData.logView(this.database.id);
this.analyticsLogEvent('mod_data_view_database');
}
/**

View File

@ -43,6 +43,8 @@ import { AddonModDataHelper } from '../../services/data-helper';
import { CoreDom } from '@singletons/dom';
import { AddonModDataEntryFieldInitialized } from '../../classes/base-field-plugin-component';
import { CoreTextUtils } from '@services/utils/text';
import { CoreTime } from '@singletons/time';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
/**
* Page that displays the view edit page.
@ -65,6 +67,7 @@ export class AddonModDataEditPage implements OnInit {
protected initialSelectedGroup?: number;
protected isEditing = false;
protected originalData: AddonModDataEntryFields = {};
protected logView: () => void;
entry?: AddonModDataEntry;
fields: Record<number, AddonModDataField> = {};
@ -94,6 +97,20 @@ export class AddonModDataEditPage implements OnInit {
constructor() {
this.siteId = CoreSites.getCurrentSiteId();
this.editForm = new FormGroup({});
this.logView = CoreTime.once(() => {
if (!this.database) {
return;
}
CoreAnalytics.logEvent({
type: CoreAnalyticsEventType.VIEW_ITEM,
ws: this.isEditing ? 'mod_data_update_entry' : 'mod_data_add_entry',
name: this.title,
data: { databaseid: this.database.id, category: 'data' },
url: '/mod/data/edit.php?' + (this.isEditing ? `d=${this.database.id}&rid=${this.entryId}` : `id=${this.moduleId}`),
});
});
}
/**
@ -230,6 +247,7 @@ export class AddonModDataEditPage implements OnInit {
}
this.editFormRender = this.displayEditFields();
this.logView();
} catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'core.course.errorgetmodule', true);
}

View File

@ -36,6 +36,8 @@ import { AddonModDataProvider,
} from '../../services/data';
import { AddonModDataHelper } from '../../services/data-helper';
import { AddonModDataSyncProvider } from '../../services/data-sync';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
import { CoreTime } from '@singletons/time';
/**
* Page that displays the view entry page.
@ -55,9 +57,9 @@ export class AddonModDataEntryPage implements OnInit, OnDestroy {
protected entryChangedObserver: CoreEventObserver; // It will observe the changed entry event.
protected fields: Record<number, AddonModDataField> = {};
protected fieldsArray: AddonModDataField[] = [];
protected logAfterFetch = true;
protected sortBy = 0;
protected sortDirection = 'DESC';
protected logView: () => void;
moduleId = 0;
courseId!: number;
@ -129,6 +131,8 @@ export class AddonModDataEntryPage implements OnInit, OnDestroy {
}
}
}, this.siteId);
this.logView = CoreTime.once(() => this.performLogView());
}
/**
@ -219,13 +223,7 @@ export class AddonModDataEntryPage implements OnInit, OnDestroy {
access: this.access,
};
if (this.logAfterFetch) {
this.logAfterFetch = false;
await CoreUtils.ignoreErrors(AddonModData.logView(this.database.id, this.database.name));
// Store module viewed because this page also updates recent accessed items block.
CoreCourse.storeModuleViewed(this.courseId, this.moduleId);
}
this.logView();
} catch (error) {
if (!refresh) {
// Some call failed, retry without using cache since it might be a new activity.
@ -250,7 +248,7 @@ export class AddonModDataEntryPage implements OnInit, OnDestroy {
this.entryId = undefined;
this.entry = undefined;
this.entryLoaded = false;
this.logAfterFetch = true;
this.logView = CoreTime.once(() => this.performLogView()); // Log again after loading data.
await this.fetchEntryData();
}
@ -310,7 +308,6 @@ export class AddonModDataEntryPage implements OnInit, OnDestroy {
this.entry = undefined;
this.entryId = undefined;
this.entryLoaded = false;
this.logAfterFetch = true;
await this.fetchEntryData();
}
@ -422,6 +419,28 @@ export class AddonModDataEntryPage implements OnInit, OnDestroy {
AddonModData.invalidateEntryData(this.database!.id, this.entryId!);
}
/**
* Log view.
*/
protected async performLogView(): Promise<void> {
if (!this.database) {
return;
}
await CoreUtils.ignoreErrors(AddonModData.logView(this.database.id));
// Store module viewed because this page also updates recent accessed items block.
CoreCourse.storeModuleViewed(this.courseId, this.moduleId);
CoreAnalytics.logEvent({
type: CoreAnalyticsEventType.VIEW_ITEM,
ws: 'mod_data_view_database',
name: this.database.name,
data: { id: this.entryId, databaseid: this.database.id, category: 'data' },
url: `/mod/data/view.php?d=${this.database.id}&rid=${this.entryId}`,
});
}
/**
* @inheritdoc
*/

View File

@ -955,23 +955,19 @@ export class AddonModDataProvider {
* Report the database as being viewed.
*
* @param id Module ID.
* @param name Name of the data.
* @param siteId Site ID. If not defined, current site.
* @returns Promise resolved when the WS call is successful.
*/
async logView(id: number, name?: string, siteId?: string): Promise<void> {
async logView(id: number, siteId?: string): Promise<void> {
const params: AddonModDataViewDatabaseWSParams = {
databaseid: id,
};
await CoreCourseLogHelper.logSingle(
await CoreCourseLogHelper.log(
'mod_data_view_database',
params,
AddonModDataProvider.COMPONENT,
id,
name,
'data',
{},
siteId,
);
}

View File

@ -37,8 +37,7 @@ export class AddonModFeedbackAttemptsSource extends CoreRoutedItemsManagerSource
anonymous?: AddonModFeedbackWSAnonAttempt[];
anonymousTotal?: number;
groupInfo?: CoreGroupInfo;
protected feedback?: AddonModFeedbackWSFeedback;
feedback?: AddonModFeedbackWSFeedback;
constructor(courseId: number, cmId: number) {
super();

View File

@ -56,7 +56,7 @@ export class AddonModFeedbackIndexComponent extends CoreCourseModuleMainActivity
@Input() group = 0;
component = AddonModFeedbackProvider.COMPONENT;
moduleName = 'feedback';
pluginName = 'feedback';
feedback?: AddonModFeedbackWSFeedback;
goPage?: number;
items: AddonModFeedbackItem[] = [];
@ -140,7 +140,18 @@ export class AddonModFeedbackIndexComponent extends CoreCourseModuleMainActivity
return; // Shouldn't happen.
}
await AddonModFeedback.logView(this.feedback.id, this.feedback.name);
await AddonModFeedback.logView(this.feedback.id);
this.callAnalyticsLogEvent();
}
/**
* Call analytics.
*/
protected callAnalyticsLogEvent(): void {
this.analyticsLogEvent('mod_feedback_view_feedback', {
url: this.tab === 'analysis' ? `/mod/feedback/analysis.php?id=${this.module.id}` : undefined,
});
}
/**
@ -429,11 +440,16 @@ export class AddonModFeedbackIndexComponent extends CoreCourseModuleMainActivity
* @param tabName New tab name.
*/
tabChanged(tabName: string): void {
const tabHasChanged = this.tab !== undefined && this.tab !== tabName;
this.tab = tabName;
if (!this.tabsLoaded[this.tab]) {
this.loadContent(false, false, true);
}
if (tabHasChanged) {
this.callAnalyticsLogEvent();
}
}
/**

View File

@ -27,6 +27,8 @@ import {
AddonModFeedbackWSFeedback,
} from '../../services/feedback';
import { AddonModFeedbackAttempt, AddonModFeedbackFormItem, AddonModFeedbackHelper } from '../../services/feedback-helper';
import { CoreTime } from '@singletons/time';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
/**
* Page that displays a feedback attempt review.
@ -48,6 +50,7 @@ export class AddonModFeedbackAttemptPage implements OnInit, OnDestroy {
loaded = false;
protected attemptId: number;
protected logView: () => void;
constructor() {
this.cmId = CoreNavigator.getRequiredRouteNumberParam('cmId');
@ -60,6 +63,21 @@ export class AddonModFeedbackAttemptPage implements OnInit, OnDestroy {
);
this.attempts = new AddonModFeedbackAttemptsSwipeManager(source);
this.logView = CoreTime.once(() => {
if (!this.feedback) {
return;
}
CoreAnalytics.logEvent({
type: CoreAnalyticsEventType.VIEW_ITEM,
ws: 'mod_feedback_get_responses_analysis',
name: this.feedback.name,
data: { id: this.attemptId, feedbackid: this.feedback.id, category: 'feedback' },
url: `/mod/feedback/show_entries.php?id=${this.cmId}` +
(this.attempt ? `userid=${this.attempt.userid}` : '' ) + `&showcompleted=${this.attemptId}`,
});
});
}
/**
@ -129,6 +147,8 @@ export class AddonModFeedbackAttemptPage implements OnInit, OnDestroy {
return attemptItem;
}).filter((itemData) => itemData); // Filter items with errors.
this.logView();
} catch (message) {
// Some call failed on fetch, go back.
CoreDomUtils.showErrorModalDefault(message, 'core.course.errorgetmodule', true);

View File

@ -25,6 +25,8 @@ import { CoreDomUtils } from '@services/utils/dom';
import { CoreUtils } from '@services/utils/utils';
import { AddonModFeedbackAttemptItem, AddonModFeedbackAttemptsSource } from '../../classes/feedback-attempts-source';
import { AddonModFeedbackWSAnonAttempt, AddonModFeedbackWSAttempt } from '../../services/feedback';
import { CoreTime } from '@singletons/time';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
/**
* Page that displays feedback attempts.
@ -41,8 +43,25 @@ export class AddonModFeedbackAttemptsPage implements AfterViewInit, OnDestroy {
fetchFailed = false;
courseId?: number;
protected logView: () => void;
constructor(protected route: ActivatedRoute) {
this.promisedAttempts = new CorePromisedValue();
this.logView = CoreTime.once(() => {
const source = this.attempts?.getSource();
if (!source || !source.feedback) {
return;
}
CoreAnalytics.logEvent({
type: CoreAnalyticsEventType.VIEW_ITEM_LIST,
ws: 'mod_feedback_get_responses_analysis',
name: source.feedback.name,
data: { feedbackid: source.feedback.id, category: 'feedback' },
url: `/mod/feedback/show_entries.php?id=${source.CM_ID}`,
});
});
}
get attempts(): AddonModFeedbackAttemptsManager | null {
@ -112,6 +131,8 @@ export class AddonModFeedbackAttemptsPage implements AfterViewInit, OnDestroy {
await attempts.getSource().loadFeedback();
await attempts.load();
this.logView();
} catch (error) {
this.fetchFailed = true;

View File

@ -38,6 +38,7 @@ import {
import { AddonModFeedbackFormItem, AddonModFeedbackHelper } from '../../services/feedback-helper';
import { AddonModFeedbackSync } from '../../services/feedback-sync';
import { AddonModFeedbackModuleHandlerService } from '../../services/handlers/module';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
/**
* Page that displays feedback form.
@ -122,7 +123,7 @@ export class AddonModFeedbackFormPage implements OnInit, OnDestroy, CanLeave {
}
try {
await AddonModFeedback.logView(this.feedback.id, this.feedback.name, true);
await AddonModFeedback.logView(this.feedback.id, true);
CoreCourse.checkModuleCompletion(this.courseId, this.module!.completiondata);
} catch {
@ -263,6 +264,8 @@ export class AddonModFeedbackFormPage implements OnInit, OnDestroy, CanLeave {
const itemsCopy = CoreUtils.clone(this.items); // Copy the array to avoid modifications.
this.originalData = AddonModFeedbackHelper.getPageItemsResponses(itemsCopy);
}
this.analyticsLogEvent();
}
/**
@ -435,6 +438,40 @@ export class AddonModFeedbackFormPage implements OnInit, OnDestroy, CanLeave {
}
}
/**
* Log event in analytics.
*/
protected analyticsLogEvent(): void {
if (!this.feedback) {
return;
}
if (this.preview) {
CoreAnalytics.logEvent({
type: CoreAnalyticsEventType.VIEW_ITEM,
ws: 'mod_feedback_get_items',
name: this.feedback.name,
data: { id: this.feedback.id, category: 'feedback' },
url: `/mod/feedback/print.php?id=${this.cmId}&courseid=${this.courseId}`,
});
return;
}
let url = '/mod/feedback/complete.php';
if (!this.completed) {
url += `?id=${this.cmId}` + (this.currentPage ? `&gopage=${this.currentPage}` : '') + `&courseid=${this.courseId}`;
}
CoreAnalytics.logEvent({
type: CoreAnalyticsEventType.VIEW_ITEM,
ws: this.completed ? 'mod_feedback_get_feedback_access_information' : 'mod_feedback_get_page_items',
name: this.feedback.name,
data: { id: this.feedback.id, category: 'feedback', page: this.currentPage },
url,
});
}
/**
* @inheritdoc
*/

View File

@ -20,6 +20,8 @@ import { CoreDomUtils } from '@services/utils/dom';
import { CoreUtils } from '@services/utils/utils';
import { AddonModFeedback, AddonModFeedbackWSFeedback } from '../../services/feedback';
import { AddonModFeedbackHelper, AddonModFeedbackNonRespondent } from '../../services/feedback-helper';
import { CoreTime } from '@singletons/time';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
/**
* Page that displays feedback non respondents.
@ -33,6 +35,7 @@ export class AddonModFeedbackNonRespondentsPage implements OnInit {
protected cmId!: number;
protected feedback?: AddonModFeedbackWSFeedback;
protected page = 0;
protected logView: () => void;
courseId!: number;
selectedGroup!: number;
@ -43,6 +46,22 @@ export class AddonModFeedbackNonRespondentsPage implements OnInit {
loaded = false;
loadMoreError = false;
constructor() {
this.logView = CoreTime.once(() => {
if (!this.feedback) {
return;
}
CoreAnalytics.logEvent({
type: CoreAnalyticsEventType.VIEW_ITEM_LIST,
ws: 'mod_feedback_get_non_respondents',
name: this.feedback.name,
data: { feedbackid: this.feedback.id, category: 'feedback' },
url: `/mod/feedback/show_nonrespondents.php?id=${this.cmId}&courseid=${this.courseId}`,
});
});
}
/**
* @inheritdoc
*/
@ -81,6 +100,8 @@ export class AddonModFeedbackNonRespondentsPage implements OnInit {
this.selectedGroup = CoreGroups.validateGroupId(this.selectedGroup, this.groupInfo);
await this.loadGroupUsers(this.selectedGroup);
this.logView();
} catch (message) {
CoreDomUtils.showErrorModalDefault(message, 'core.course.errorgetmodule', true);

View File

@ -1093,25 +1093,21 @@ export class AddonModFeedbackProvider {
* Report the feedback as being viewed.
*
* @param id Module ID.
* @param name Name of the feedback.
* @param formViewed True if form was viewed.
* @param siteId Site ID. If not defined, current site.
* @returns Promise resolved when the WS call is successful.
*/
async logView(id: number, name?: string, formViewed: boolean = false, siteId?: string): Promise<void> {
async logView(id: number, formViewed: boolean = false, siteId?: string): Promise<void> {
const params: AddonModFeedbackViewFeedbackWSParams = {
feedbackid: id,
moduleviewed: formViewed,
};
await CoreCourseLogHelper.logSingle(
await CoreCourseLogHelper.log(
'mod_feedback_view_feedback',
params,
AddonModFeedbackProvider.COMPONENT,
id,
name,
'feedback',
{ moduleviewed: params.moduleviewed },
siteId,
);
}

View File

@ -22,6 +22,7 @@ import { Md5 } from 'ts-md5';
import { AddonModFolder, AddonModFolderFolder, AddonModFolderProvider } from '../../services/folder';
import { AddonModFolderFolderFormattedData, AddonModFolderHelper } from '../../services/folder-helper';
import { AddonModFolderModuleHandlerService } from '../../services/handlers/module';
import { CoreUtils } from '@services/utils/utils';
/**
* Component that displays a folder.
@ -39,6 +40,7 @@ export class AddonModFolderIndexComponent extends CoreCourseModuleMainResourceCo
@Input() subfolder?: AddonModFolderFolderFormattedData; // Subfolder to show.
component = AddonModFolderProvider.COMPONENT;
pluginName = 'folder';
contents?: AddonModFolderFolderFormattedData;
constructor(@Optional() courseContentsPage?: CoreCourseContentsPage) {
@ -119,7 +121,9 @@ export class AddonModFolderIndexComponent extends CoreCourseModuleMainResourceCo
* @inheritdoc
*/
protected async logActivity(): Promise<void> {
await AddonModFolder.logView(this.module.instance, this.module.name);
await CoreUtils.ignoreErrors(AddonModFolder.logView(this.module.instance));
this.analyticsLogEvent('mod_folder_view_folder');
}
/**

View File

@ -126,23 +126,19 @@ export class AddonModFolderProvider {
* Report a folder as being viewed.
*
* @param id Module ID.
* @param name Name of the folder.
* @param siteId Site ID. If not defined, current site.
* @returns Promise resolved when the WS call is successful.
*/
async logView(id: number, name?: string, siteId?: string): Promise<void> {
async logView(id: number, siteId?: string): Promise<void> {
const params: AddonModFolderViewFolderWSParams = {
folderid: id,
};
await CoreCourseLogHelper.logSingle(
await CoreCourseLogHelper.log(
'mod_folder_view_folder',
params,
AddonModFolderProvider.COMPONENT,
id,
name,
'folder',
{},
siteId,
);
}

View File

@ -71,7 +71,7 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
@ViewChild(CoreSplitViewComponent) splitView!: CoreSplitViewComponent;
component = AddonModForumProvider.COMPONENT;
moduleName = 'forum';
pluginName = 'forum';
descriptionNote?: string;
promisedDiscussions: CorePromisedValue<AddonModForumDiscussionsManager>;
discussionsItems: AddonModForumDiscussionItem[] = [];
@ -708,12 +708,14 @@ class AddonModForumDiscussionsManager extends CoreListItemsManager<AddonModForum
}
try {
await AddonModForum.logView(forum.id, forum.name);
await AddonModForum.logView(forum.id);
CoreCourse.checkModuleCompletion(this.page.courseId, this.page.module.completiondata);
} catch {
// Ignore errors.
}
this.page.analyticsLogEvent('mod_forum_view_forum');
}
/**

View File

@ -52,6 +52,7 @@ import { CoreForms } from '@singletons/form';
import { CoreFileEntry } from '@services/file-helper';
import { AddonModForumSharedPostFormData } from '../../pages/discussion/discussion';
import { CoreDom } from '@singletons/dom';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
/**
* Components that shows a discussion post, its attachments and the action buttons allowed (reply, etc.).
@ -141,6 +142,9 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges
* Deletes an online post.
*/
async deletePost(): Promise<void> {
// Log analytics even if the user cancels for consistency with LMS.
this.analyticsLogEvent('mod_forum_delete_post', `/mod/forum/post.php?delete=${this.post.id}`);
try {
await CoreDomUtils.showDeleteConfirm('addon.mod_forum.deletesure');
@ -290,6 +294,8 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges
}
this.scrollToForm();
this.analyticsLogEvent('mod_forum_add_discussion_post', `/mod/forum/post.php?reply=${this.post.id}`);
}
/**
@ -314,6 +320,8 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges
);
this.scrollToForm();
this.analyticsLogEvent('mod_forum_update_discussion_post', `/mod/forum/post.php?edit=${this.post.id}`);
} catch {
// Cancelled.
}
@ -554,4 +562,24 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges
);
}
/**
* Log analytics event.
*
* @param wsName WS name.
* @param url URL.
*/
protected analyticsLogEvent(wsName: string, url: string): void {
if (this.post.id <= 0) {
return;
}
CoreAnalytics.logEvent({
type: CoreAnalyticsEventType.VIEW_ITEM,
ws: wsName,
name: this.post.subject,
data: { id: this.post.id, forumid: this.forum.id, category: 'forum' },
url,
});
}
}

View File

@ -51,6 +51,7 @@ import {
import { AddonModForumHelper } from '../../services/forum-helper';
import { AddonModForumOffline } from '../../services/forum-offline';
import { AddonModForumSync, AddonModForumSyncProvider } from '../../services/forum-sync';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
type SortType = 'flat-newest' | 'flat-oldest' | 'nested';
@ -562,19 +563,7 @@ export class AddonModForumDiscussionPage implements OnInit, AfterViewInit, OnDes
if (forceMarkAsRead || (hasUnreadPosts && this.trackPosts)) {
// Add log in Moodle and mark unread posts as readed.
AddonModForum.logDiscussionView(this.discussionId, this.forumId || -1, this.forum.name).catch(() => {
// Ignore errors.
}).finally(() => {
if (!this.courseId || !this.cmId) {
return;
}
// Trigger mark read posts.
CoreEvents.trigger(AddonModForumProvider.MARK_READ_EVENT, {
courseId: this.courseId,
moduleId: this.cmId,
}, CoreSites.getCurrentSiteId());
});
this.logDiscussionView(forceMarkAsRead);
}
}
}
@ -854,6 +843,35 @@ export class AddonModForumDiscussionPage implements OnInit, AfterViewInit, OnDes
return posts;
}
/**
* Log discussion as viewed. This will also mark the posts as read.
*
* @param logAnalytics Whether to log analytics too or not.
*/
protected async logDiscussionView(logAnalytics = false): Promise<void> {
await CoreUtils.ignoreErrors(AddonModForum.logDiscussionView(this.discussionId, this.forumId || -1));
if (logAnalytics) {
CoreAnalytics.logEvent({
type: CoreAnalyticsEventType.VIEW_ITEM,
ws: 'mod_forum_view_forum_discussion',
name: this.startingPost?.subject ?? this.forum.name ?? '',
data: { id: this.discussionId, forumid: this.forumId, category: 'forum' },
url: `/mod/forum/discuss.php?d=${this.discussionId}` + (this.postId ? `#p${this.postId}` : ''),
});
}
if (!this.courseId || !this.cmId) {
return;
}
// Trigger mark read posts.
CoreEvents.trigger(AddonModForumProvider.MARK_READ_EVENT, {
courseId: this.courseId,
moduleId: this.cmId,
}, CoreSites.getCurrentSiteId());
}
}
/**

View File

@ -44,6 +44,8 @@ import { AddonModForumDiscussionsSwipeManager } from '../../classes/forum-discus
import { ActivatedRoute, ActivatedRouteSnapshot } from '@angular/router';
import { AddonModForumDiscussionsSource } from '../../classes/forum-discussions-source';
import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker';
import { CoreTime } from '@singletons/time';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
type NewDiscussionData = {
subject: string;
@ -105,8 +107,19 @@ export class AddonModForumNewDiscussionPage implements OnInit, OnDestroy, CanLea
protected originalData?: Partial<NewDiscussionData>;
protected forceLeave = false;
protected initialGroupId?: number;
protected logView: () => void;
constructor(protected route: ActivatedRoute, @Optional() protected splitView: CoreSplitViewComponent) {}
constructor(protected route: ActivatedRoute, @Optional() protected splitView: CoreSplitViewComponent) {
this.logView = CoreTime.once(() => {
CoreAnalytics.logEvent({
type: CoreAnalyticsEventType.VIEW_ITEM,
ws: 'mod_forum_add_discussion',
name: Translate.instant('addon.mod_forum.addanewdiscussion'),
data: { id: this.forumId, category: 'forum' },
url: '/mod/forum/post.php',
});
});
}
/**
* @inheritdoc
@ -309,6 +322,8 @@ export class AddonModForumNewDiscussionPage implements OnInit, OnDestroy, CanLea
}
this.showForm = true;
this.logView();
} catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'addon.mod_forum.errorgetgroups', true);

View File

@ -996,23 +996,19 @@ export class AddonModForumProvider {
* Report a forum as being viewed.
*
* @param id Module ID.
* @param name Name of the forum.
* @param siteId Site ID. If not defined, current site.
* @returns Promise resolved when the WS call is successful.
*/
logView(id: number, name?: string, siteId?: string): Promise<void> {
logView(id: number, siteId?: string): Promise<void> {
const params = {
forumid: id,
};
return CoreCourseLogHelper.logSingle(
return CoreCourseLogHelper.log(
'mod_forum_view_forum',
params,
AddonModForumProvider.COMPONENT,
id,
name,
'forum',
{},
siteId,
);
}
@ -1022,23 +1018,19 @@ export class AddonModForumProvider {
*
* @param id Discussion ID.
* @param forumId Forum ID.
* @param name Name of the forum.
* @param siteId Site ID. If not defined, current site.
* @returns Promise resolved when the WS call is successful.
*/
logDiscussionView(id: number, forumId: number, name?: string, siteId?: string): Promise<void> {
logDiscussionView(id: number, forumId: number, siteId?: string): Promise<void> {
const params = {
discussionid: id,
};
return CoreCourseLogHelper.logSingle(
return CoreCourseLogHelper.log(
'mod_forum_view_forum_discussion',
params,
AddonModForumProvider.COMPONENT,
forumId,
name,
'forum',
params,
siteId,
);
}

View File

@ -71,7 +71,7 @@ export class AddonModGlossaryIndexComponent extends CoreCourseModuleMainActivity
@ViewChild(CoreSplitViewComponent) splitView!: CoreSplitViewComponent;
component = AddonModGlossaryProvider.COMPONENT;
moduleName = 'glossary';
pluginName = 'glossary';
canAdd = false;
loadMoreError = false;
@ -482,12 +482,14 @@ class AddonModGlossaryEntriesManager extends CoreListItemsManager<AddonModGlossa
}
try {
await AddonModGlossary.logView(glossary.id, viewMode, glossary.name);
await AddonModGlossary.logView(glossary.id, viewMode);
CoreCourse.checkModuleCompletion(this.page.courseId, this.page.module.completiondata);
} catch {
// Ignore errors.
}
this.page.analyticsLogEvent('mod_glossary_view_glossary', { data: { mode: viewMode } });
}
/**

View File

@ -40,6 +40,7 @@ import {
} from '../../services/glossary';
import { AddonModGlossaryHelper } from '../../services/glossary-helper';
import { AddonModGlossaryOffline } from '../../services/glossary-offline';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
/**
* Page that displays the edit form.
@ -76,6 +77,7 @@ export class AddonModGlossaryEditPage implements OnInit, CanLeave {
originalData?: AddonModGlossaryFormData;
protected entry?: AddonModGlossaryEntry;
protected syncId?: string;
protected syncObserver?: CoreEventObserver;
protected isDestroyed = false;
@ -99,6 +101,7 @@ export class AddonModGlossaryEditPage implements OnInit, CanLeave {
} else if (entrySlug) {
const { entry } = await AddonModGlossary.getEntry(Number(entrySlug));
this.entry = entry;
this.editorExtraParams.timecreated = entry.timecreated;
this.handler = new AddonModGlossaryOnlineFormHandler(this, entry);
} else {
@ -127,6 +130,18 @@ export class AddonModGlossaryEditPage implements OnInit, CanLeave {
await this.handler.loadData(this.glossary);
this.loaded = true;
if (this.handler instanceof AddonModGlossaryOfflineFormHandler) {
return;
}
CoreAnalytics.logEvent({
type: CoreAnalyticsEventType.VIEW_ITEM,
ws: 'mod_glossary_get_glossaries_by_courses',
name: this.glossary.name,
data: { id: this.glossary.id, category: 'glossary' },
url: '/mod/glossary/edit.php' + (this.entry ? `?cmid=${this.cmId}&id=${this.entry.id}` : ''),
});
} catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'addon.mod_glossary.errorloadingglossary', true);

View File

@ -39,6 +39,8 @@ import {
AddonModGlossaryProvider,
GLOSSARY_ENTRY_UPDATED,
} from '../../services/glossary';
import { CoreTime } from '@singletons/time';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
/**
* Page that displays a glossary entry.
@ -70,7 +72,19 @@ export class AddonModGlossaryEntryPage implements OnInit, OnDestroy {
courseId!: number;
cmId!: number;
constructor(@Optional() protected splitView: CoreSplitViewComponent, protected route: ActivatedRoute) {}
protected logView: () => void;
constructor(@Optional() protected splitView: CoreSplitViewComponent, protected route: ActivatedRoute) {
this.logView = CoreTime.once(async () => {
if (!this.onlineEntry || !this.glossary || !this.componentId) {
return;
}
await CoreUtils.ignoreErrors(AddonModGlossary.logEntryView(this.onlineEntry.id, this.componentId));
this.analyticsLogEvent('mod_glossary_get_entry_by_id', `/mod/glossary/showentry.php?eid=${this.onlineEntry.id}`);
});
}
get entry(): AddonModGlossaryEntry | AddonModGlossaryOfflineEntry | undefined {
return this.onlineEntry ?? this.offlineEntry;
@ -128,12 +142,6 @@ export class AddonModGlossaryEntryPage implements OnInit, OnDestroy {
try {
if (onlineEntryId) {
await this.loadOnlineEntry(onlineEntryId);
if (!this.glossary || !this.componentId) {
return;
}
await CoreUtils.ignoreErrors(AddonModGlossary.logEntryView(onlineEntryId, this.componentId, this.glossary?.name));
} else if (offlineEntryTimeCreated) {
await this.loadOfflineEntry(offlineEntryTimeCreated);
}
@ -161,6 +169,12 @@ export class AddonModGlossaryEntryPage implements OnInit, OnDestroy {
* Delete entry.
*/
async deleteEntry(): Promise<void> {
// Log analytics even if the user cancels for consistency with LMS.
this.analyticsLogEvent(
'mod_glossary_delete_entry',
`/mod/glossary/deleteentry.php?id=${this.glossary?.id}&mode=delete&entry=${this.onlineEntry?.id}`,
);
const glossaryId = this.glossary?.id;
const cancelled = await CoreUtils.promiseFails(
CoreDomUtils.showConfirm(Translate.instant('addon.mod_glossary.areyousuredelete')),
@ -250,6 +264,8 @@ export class AddonModGlossaryEntryPage implements OnInit, OnDestroy {
this.canEdit = canUpdateEntries && !!result.permissions?.canupdate;
await this.loadGlossary();
this.logView();
} catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'addon.mod_glossary.errorloadingentry', true);
}
@ -321,6 +337,26 @@ export class AddonModGlossaryEntryPage implements OnInit, OnDestroy {
AddonModGlossary.invalidateEntry(this.onlineEntry.id);
}
/**
* Log analytics event.
*
* @param wsName WS name.
* @param url URL.
*/
protected analyticsLogEvent(wsName: string, url: string): void {
if (!this.onlineEntry || !this.glossary) {
return;
}
CoreAnalytics.logEvent({
type: CoreAnalyticsEventType.VIEW_ITEM,
ws: wsName,
name: this.onlineEntry.concept,
data: { id: this.onlineEntry.id, glossaryid: this.glossary.id, category: 'glossary' },
url,
});
}
}
/**

View File

@ -1020,23 +1020,19 @@ export class AddonModGlossaryProvider {
*
* @param glossaryId Glossary ID.
* @param mode The mode in which the glossary was viewed.
* @param name Name of the glossary.
* @param siteId Site ID. If not defined, current site.
*/
async logView(glossaryId: number, mode: string, name?: string, siteId?: string): Promise<void> {
async logView(glossaryId: number, mode: string, siteId?: string): Promise<void> {
const params: AddonModGlossaryViewGlossaryWSParams = {
id: glossaryId,
mode: mode,
};
await CoreCourseLogHelper.logSingle(
await CoreCourseLogHelper.log(
'mod_glossary_view_glossary',
params,
AddonModGlossaryProvider.COMPONENT,
glossaryId,
name,
'glossary',
{ mode },
siteId,
);
}
@ -1046,22 +1042,18 @@ export class AddonModGlossaryProvider {
*
* @param entryId Entry ID.
* @param glossaryId Glossary ID.
* @param name Name of the glossary.
* @param siteId Site ID. If not defined, current site.
*/
async logEntryView(entryId: number, glossaryId: number, name?: string, siteId?: string): Promise<void> {
async logEntryView(entryId: number, glossaryId: number, siteId?: string): Promise<void> {
const params: AddonModGlossaryViewEntryWSParams = {
id: entryId,
};
await CoreCourseLogHelper.logSingle(
await CoreCourseLogHelper.log(
'mod_glossary_view_entry',
params,
AddonModGlossaryProvider.COMPONENT,
glossaryId,
name,
'glossary',
{ entryid: entryId },
siteId,
);
}

View File

@ -63,7 +63,7 @@ export class AddonModH5PActivityIndexComponent extends CoreCourseModuleMainActiv
@Output() onActivityFinish = new EventEmitter<boolean>();
component = AddonModH5PActivityProvider.COMPONENT;
moduleName = 'h5pactivity';
pluginName = 'h5pactivity';
h5pActivity?: AddonModH5PActivityData; // The H5P activity object.
accessInfo?: AddonModH5PActivityAccessInfo; // Info about the user capabilities.
@ -441,9 +441,11 @@ export class AddonModH5PActivityIndexComponent extends CoreCourseModuleMainActiv
this.playing = true;
// Mark the activity as viewed.
await AddonModH5PActivity.logView(this.h5pActivity.id, this.h5pActivity.name, this.siteId);
await AddonModH5PActivity.logView(this.h5pActivity.id, this.siteId);
this.checkCompletion();
this.analyticsLogEvent('mod_h5pactivity_view_h5pactivity');
}
/**

View File

@ -25,6 +25,8 @@ import {
AddonModH5PActivityData,
AddonModH5PActivityAttemptResults,
} from '../../services/h5pactivity';
import { CoreTime } from '@singletons/time';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
/**
* Page that displays results of an attempt.
@ -45,7 +47,28 @@ export class AddonModH5PActivityAttemptResultsPage implements OnInit {
cmId!: number;
protected attemptId!: number;
protected fetchSuccess = false;
protected logView: () => void;
constructor() {
this.logView = CoreTime.once(async () => {
if (!this.h5pActivity) {
return;
}
await CoreUtils.ignoreErrors(AddonModH5PActivity.logViewReport(
this.h5pActivity.id,
{ attemptId: this.attemptId },
));
CoreAnalytics.logEvent({
type: CoreAnalyticsEventType.VIEW_ITEM,
ws: 'mod_h5pactivity_log_report_viewed',
name: this.h5pActivity.name,
data: { id: this.h5pActivity.id, attemptid: this.attemptId, category: 'h5pactivity' },
url: `/mod/h5pactivity/report.php?a=${this.h5pActivity.id}&attemptid=${this.attemptId}`,
});
});
}
/**
* @inheritdoc
@ -92,14 +115,7 @@ export class AddonModH5PActivityAttemptResultsPage implements OnInit {
await this.fetchUserProfile();
if (!this.fetchSuccess) {
this.fetchSuccess = true;
CoreUtils.ignoreErrors(AddonModH5PActivity.logViewReport(
this.h5pActivity.id,
this.h5pActivity.name,
{ attemptId: this.attemptId },
));
}
this.logView();
} catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'Error loading attempt.');
} finally {

View File

@ -26,6 +26,8 @@ import {
AddonModH5PActivityData,
AddonModH5PActivityUserAttempts,
} from '../../services/h5pactivity';
import { CoreTime } from '@singletons/time';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
/**
* Page that displays user attempts of a certain user.
@ -46,7 +48,28 @@ export class AddonModH5PActivityUserAttemptsPage implements OnInit {
isCurrentUser = false;
protected userId!: number;
protected fetchSuccess = false;
protected logView: () => void;
constructor() {
this.logView = CoreTime.once(async () => {
if (!this.h5pActivity) {
return;
}
await CoreUtils.ignoreErrors(AddonModH5PActivity.logViewReport(
this.h5pActivity.id,
{ userId: this.userId },
));
CoreAnalytics.logEvent({
type: CoreAnalyticsEventType.VIEW_ITEM,
ws: 'mod_h5pactivity_log_report_viewed',
name: this.h5pActivity.name,
data: { id: this.h5pActivity.id, userid: this.userId, category: 'h5pactivity' },
url: `/mod/h5pactivity/report.php?a=${this.h5pActivity.id}&userid=${this.userId}`,
});
});
}
/**
* @inheritdoc
@ -94,14 +117,7 @@ export class AddonModH5PActivityUserAttemptsPage implements OnInit {
this.fetchUserProfile(),
]);
if (!this.fetchSuccess) {
this.fetchSuccess = true;
CoreUtils.ignoreErrors(AddonModH5PActivity.logViewReport(
this.h5pActivity.id,
this.h5pActivity.name,
{ userId: this.userId },
));
}
this.logView();
} catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'Error loading attempts.');
} finally {

View File

@ -25,6 +25,8 @@ import {
AddonModH5PActivityProvider,
AddonModH5PActivityUserAttempts,
} from '../../services/h5pactivity';
import { CoreTime } from '@singletons/time';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
/**
* Page that displays all users that can attempt an H5P activity.
@ -45,7 +47,25 @@ export class AddonModH5PActivityUsersAttemptsPage implements OnInit {
canLoadMore = false;
protected page = 0;
protected fetchSuccess = false;
protected logView: () => void;
constructor() {
this.logView = CoreTime.once(async () => {
if (!this.h5pActivity) {
return;
}
await CoreUtils.ignoreErrors(AddonModH5PActivity.logViewReport(this.h5pActivity.id));
CoreAnalytics.logEvent({
type: CoreAnalyticsEventType.VIEW_ITEM_LIST,
ws: 'mod_h5pactivity_log_report_viewed',
name: this.h5pActivity.name,
data: { id: this.h5pActivity.id, category: 'h5pactivity' },
url: `/mod/h5pactivity/report.php?a=${this.h5pActivity.id}`,
});
});
}
/**
* @inheritdoc
@ -90,10 +110,7 @@ export class AddonModH5PActivityUsersAttemptsPage implements OnInit {
this.fetchUsers(refresh),
]);
if (!this.fetchSuccess) {
this.fetchSuccess = true;
CoreUtils.ignoreErrors(AddonModH5PActivity.logViewReport(this.h5pActivity.id, this.h5pActivity.name));
}
this.logView();
} catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'Error loading attempts.');
} finally {

View File

@ -776,23 +776,19 @@ export class AddonModH5PActivityProvider {
* Report an H5P activity as being viewed.
*
* @param id H5P activity ID.
* @param name Name of the activity.
* @param siteId Site ID. If not defined, current site.
* @returns Promise resolved when the WS call is successful.
*/
logView(id: number, name?: string, siteId?: string): Promise<void> {
logView(id: number, siteId?: string): Promise<void> {
const params: AddonModH5PActivityViewH5pactivityWSParams = {
h5pactivityid: id,
};
return CoreCourseLogHelper.logSingle(
return CoreCourseLogHelper.log(
'mod_h5pactivity_view_h5pactivity',
params,
AddonModH5PActivityProvider.COMPONENT,
id,
name,
'h5pactivity',
{},
siteId,
);
}
@ -801,11 +797,10 @@ export class AddonModH5PActivityProvider {
* Report an H5P activity report as being viewed.
*
* @param id H5P activity ID.
* @param name Name of the activity.
* @param options Options.
* @returns Promise resolved when the WS call is successful.
*/
async logViewReport(id: number, name?: string, options: AddonModH5PActivityViewReportOptions = {}): Promise<void> {
async logViewReport(id: number, options: AddonModH5PActivityViewReportOptions = {}): Promise<void> {
const site = await CoreSites.getSite(options.siteId);
if (!site.wsAvailable('mod_h5pactivity_log_report_viewed')) {
@ -819,14 +814,11 @@ export class AddonModH5PActivityProvider {
attemptid: options.attemptId,
};
return CoreCourseLogHelper.logSingle(
return CoreCourseLogHelper.log(
'mod_h5pactivity_log_report_viewed',
params,
AddonModH5PActivityProvider.COMPONENT,
id,
name,
'h5pactivity',
{},
site.getId(),
);
}

View File

@ -18,6 +18,7 @@ import { CoreCourseContentsPage } from '@features/course/pages/contents/contents
import { CoreCourse } from '@features/course/services/course';
import { CoreNavigator } from '@services/navigator';
import { AddonModImscpProvider, AddonModImscp, AddonModImscpTocItem } from '../../services/imscp';
import { CoreUtils } from '@services/utils/utils';
/**
* Component that displays a IMSCP.
@ -30,6 +31,7 @@ import { AddonModImscpProvider, AddonModImscp, AddonModImscpTocItem } from '../.
export class AddonModImscpIndexComponent extends CoreCourseModuleMainResourceComponent implements OnInit {
component = AddonModImscpProvider.COMPONENT;
pluginName = 'imscp';
items: AddonModImscpTocItem[] = [];
hasStarted = false;
@ -100,7 +102,9 @@ export class AddonModImscpIndexComponent extends CoreCourseModuleMainResourceCom
* @inheritdoc
*/
protected async logActivity(): Promise<void> {
await AddonModImscp.logView(this.module.instance, this.module.name);
await CoreUtils.ignoreErrors(AddonModImscp.logView(this.module.instance));
this.analyticsLogEvent('mod_imscp_view_imscp');
}
/**

View File

@ -271,7 +271,6 @@ export class AddonModImscpProvider {
* Report a IMSCP as being viewed.
*
* @param id Module ID.
* @param name Name of the imscp.
* @param siteId Site ID. If not defined, current site.
* @returns Promise resolved when the WS call is successful.
*/
@ -280,14 +279,11 @@ export class AddonModImscpProvider {
imscpid: id,
};
await CoreCourseLogHelper.logSingle(
await CoreCourseLogHelper.log(
'mod_imscp_view_imscp',
params,
AddonModImscpProvider.COMPONENT,
id,
name,
'imscp',
{},
siteId,
);
}

View File

@ -66,7 +66,7 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo
@Input() action?: string; // The "action" to display first.
component = AddonModLessonProvider.COMPONENT;
moduleName = 'lesson';
pluginName = 'lesson';
lesson?: AddonModLessonLessonWSData; // The lesson.
selectedTab?: number; // The initial selected tab.
@ -372,7 +372,16 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo
return;
}
await AddonModLesson.logViewLesson(this.lesson.id, this.password, this.lesson.name);
await CoreUtils.ignoreErrors(AddonModLesson.logViewLesson(this.lesson.id, this.password));
}
/**
* Call analytics.
*/
protected callAnalyticsLogEvent(): void {
this.analyticsLogEvent('mod_lesson_view_lesson', {
url: this.selectedTab === 1 ? `/mod/lesson/report.php?id=${this.module.id}&action=reportoverview` : undefined,
});
}
/**
@ -435,13 +444,19 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo
* First tab selected.
*/
indexSelected(): void {
const tabHasChanged = this.selectedTab !== 0;
this.selectedTab = 0;
if (tabHasChanged) {
this.callAnalyticsLogEvent();
}
}
/**
* Reports tab selected.
*/
reportsSelected(): void {
const tabHasChanged = this.selectedTab !== 1;
this.selectedTab = 1;
if (!this.groupInfo) {
@ -449,6 +464,10 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo
CoreDomUtils.showErrorModalDefault(error, 'Error getting report.');
});
}
if (tabHasChanged) {
this.callAnalyticsLogEvent();
}
}
/**

View File

@ -54,6 +54,7 @@ import {
import { AddonModLessonOffline } from '../../services/lesson-offline';
import { AddonModLessonSync } from '../../services/lesson-sync';
import { CoreFormFields, CoreForms } from '@singletons/form';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
/**
* Page that allows attempting and reviewing a lesson.
@ -446,6 +447,8 @@ export class AddonModLessonPlayerPage implements OnInit, OnDestroy, CanLeave {
this.reviewPageId = Number(params.pageid);
}
}
this.logPageLoaded(AddonModLessonProvider.LESSON_EOL, Translate.instant('addon.mod_lesson.congratulations'));
}
/**
@ -615,6 +618,44 @@ export class AddonModLessonPlayerPage implements OnInit, OnDestroy, CanLeave {
} else {
this.showRetake = false;
}
this.logPageLoaded(pageId, data.page?.title ?? '');
}
/**
* Log page loaded.
*
* @param pageId Page ID.
*/
protected logPageLoaded(pageId: number, title: string): void {
if (!this.lesson) {
return;
}
CoreAnalytics.logEvent({
type: CoreAnalyticsEventType.VIEW_ITEM,
ws: 'mod_lesson_get_page_data',
name: this.lesson.name + ': ' + title,
data: { id: this.lesson.id, pageid: pageId, category: 'lesson' },
url: `/mod/lesson/view.php?id=${this.lesson.id}&pageid=${pageId}`,
});
}
/**
* Log continue page.
*/
protected logContinuePageLoaded(): void {
if (!this.lesson) {
return;
}
CoreAnalytics.logEvent({
type: CoreAnalyticsEventType.VIEW_ITEM,
ws: 'mod_lesson_process_page',
name: this.lesson.name + ': ' + Translate.instant('addon.mod_lesson.continue'),
data: { id: this.lesson.id, category: 'lesson' },
url: '/mod/lesson/continue.php',
});
}
/**
@ -715,6 +756,8 @@ export class AddonModLessonPlayerPage implements OnInit, OnDestroy, CanLeave {
pageId: result.newpageid,
});
}
this.logContinuePageLoaded();
} catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'Error processing page');
} finally {

View File

@ -35,6 +35,7 @@ import {
} from '../../services/lesson';
import { AddonModLessonAnswerData, AddonModLessonHelper } from '../../services/lesson-helper';
import { CoreTime } from '@singletons/time';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
/**
* Page that displays a retake made by a certain user.
@ -59,6 +60,11 @@ export class AddonModLessonUserRetakePage implements OnInit {
protected userId?: number; // User ID to see the retakes.
protected retakeNumber?: number; // Number of the initial retake to see.
protected previousSelectedRetake?: number; // To be able to detect the previous selected retake when it has changed.
protected logView: () => void;
constructor() {
this.logView = CoreTime.once(() => this.performLogView());
}
/**
* @inheritdoc
@ -93,6 +99,8 @@ export class AddonModLessonUserRetakePage implements OnInit {
try {
await this.setRetake(retakeNumber);
this.performLogView();
} catch (error) {
this.selectedRetake = this.previousSelectedRetake ?? this.selectedRetake;
CoreDomUtils.showErrorModal(CoreUtils.addDataNotDownloadedError(error, 'Error getting attempt.'));
@ -160,6 +168,8 @@ export class AddonModLessonUserRetakePage implements OnInit {
this.student.profileimageurl = user?.profileimageurl;
await this.setRetake(this.selectedRetake);
this.logView();
} catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'Error getting data.', true);
}
@ -243,6 +253,23 @@ export class AddonModLessonUserRetakePage implements OnInit {
return formattedData;
}
/**
* Log view.
*/
protected performLogView(): void {
if (!this.lesson) {
return;
}
CoreAnalytics.logEvent({
type: CoreAnalyticsEventType.VIEW_ITEM,
ws: 'mod_lesson_get_user_attempt',
name: this.lesson.name + ': ' + Translate.instant('addon.mod_lesson.detailedstats'),
data: { id: this.lesson.id, userid: this.userId, try: this.selectedRetake, category: 'lesson' },
url: `/mod/lesson/report.php?id=${this.cmId}&action=reportdetail&userid=${this.userId}&try=${this.selectedRetake}`,
});
}
}
/**

View File

@ -2951,11 +2951,10 @@ export class AddonModLessonProvider {
*
* @param id Module ID.
* @param password Lesson password (if any).
* @param name Name of the assign.
* @param siteId Site ID. If not defined, current site.
* @returns Promise resolved when the WS call is successful.
*/
async logViewLesson(id: number, password?: string, name?: string, siteId?: string): Promise<void> {
async logViewLesson(id: number, password?: string, siteId?: string): Promise<void> {
const params: AddonModLessonViewLessonWSParams = {
lessonid: id,
};
@ -2964,14 +2963,11 @@ export class AddonModLessonProvider {
params.password = password;
}
await CoreCourseLogHelper.logSingle(
await CoreCourseLogHelper.log(
'mod_lesson_view_lesson',
params,
AddonModLessonProvider.COMPONENT,
id,
name,
'lesson',
{},
siteId,
);
}

View File

@ -30,7 +30,7 @@ import { AddonModLtiHelper } from '../../services/lti-helper';
export class AddonModLtiIndexComponent extends CoreCourseModuleMainActivityComponent implements OnInit {
component = AddonModLtiProvider.COMPONENT;
moduleName = 'lti';
pluginName = 'lti';
displayDescription = false;
lti?: AddonModLtiLti; // The LTI object.

View File

@ -22,6 +22,7 @@ import { CoreDomUtils } from '@services/utils/dom';
import { makeSingleton } from '@singletons';
import { CoreEvents } from '@singletons/events';
import { AddonModLti, AddonModLtiLti } from './lti';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
/**
* Service that provides some helper functions for LTI.
@ -86,7 +87,7 @@ export class AddonModLtiHelperProvider {
const launchData = await AddonModLti.getLtiLaunchData(lti.id);
// "View" LTI without blocking the UI.
this.logViewAndCheckCompletion(courseId, module, lti.id, lti.name, siteId);
this.logViewAndCheckCompletion(courseId, module, lti.id, siteId);
// Launch LTI.
return AddonModLti.launch(launchData.endpoint, launchData.parameters);
@ -111,16 +112,23 @@ export class AddonModLtiHelperProvider {
courseId: number,
module: CoreCourseModuleData,
ltiId: number,
name?: string,
siteId?: string,
): Promise<void> {
try {
await AddonModLti.logView(ltiId, name, siteId);
await AddonModLti.logView(ltiId,siteId);
CoreCourse.checkModuleCompletion(courseId, module.completiondata);
} catch {
// Ignore errors.
}
CoreAnalytics.logEvent({
type: CoreAnalyticsEventType.VIEW_ITEM,
ws: 'mod_lti_view_lti',
name: module.name,
data: { id: module.instance, category: 'lti' },
url: `/mod/lti/view.php?id=${module.id}`,
});
}
}

View File

@ -272,14 +272,11 @@ export class AddonModLtiProvider {
ltiid: id,
};
return CoreCourseLogHelper.logSingle(
return CoreCourseLogHelper.log(
'mod_lti_view_lti',
params,
AddonModLtiProvider.COMPONENT,
id,
name,
'lti',
{},
siteId,
);
}

View File

@ -31,6 +31,7 @@ import { AddonModPageHelper } from '../../services/page-helper';
export class AddonModPageIndexComponent extends CoreCourseModuleMainResourceComponent implements OnInit {
component = AddonModPageProvider.COMPONENT;
pluginName = 'page';
contents?: string;
displayDescription = false;
displayTimemodified = true;
@ -114,7 +115,9 @@ export class AddonModPageIndexComponent extends CoreCourseModuleMainResourceComp
* @inheritdoc
*/
protected async logActivity(): Promise<void> {
await AddonModPage.logView(this.module.instance, this.module.name);
await CoreUtils.ignoreErrors(AddonModPage.logView(this.module.instance));
this.analyticsLogEvent('mod_page_view_page');
}
}

View File

@ -141,23 +141,19 @@ export class AddonModPageProvider {
* Report a page as being viewed.
*
* @param pageid Module ID.
* @param name Name of the page.
* @param siteId Site ID. If not defined, current site.
* @returns Promise resolved when the WS call is successful.
*/
logView(pageid: number, name?: string, siteId?: string): Promise<void> {
logView(pageid: number, siteId?: string): Promise<void> {
const params: AddonModPageViewPageWSParams = {
pageid,
};
return CoreCourseLogHelper.logSingle(
return CoreCourseLogHelper.log(
'mod_page_view_page',
params,
AddonModPageProvider.COMPONENT,
pageid,
name,
'page',
{},
siteId,
);
}

View File

@ -57,7 +57,7 @@ import {
export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComponent implements OnInit, OnDestroy {
component = AddonModQuizProvider.COMPONENT;
moduleName = 'quiz';
pluginName = 'quiz';
quiz?: AddonModQuizQuizData; // The quiz.
now?: number; // Current time.
syncTime?: string; // Last synchronization time.
@ -386,7 +386,9 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp
return; // Shouldn't happen.
}
await AddonModQuiz.logViewQuiz(this.quiz.id, this.quiz.name);
await CoreUtils.ignoreErrors(AddonModQuiz.logViewQuiz(this.quiz.id));
this.analyticsLogEvent('mod_quiz_view_quiz');
}
/**

View File

@ -49,6 +49,7 @@ import { CoreDom } from '@singletons/dom';
import { CoreTime } from '@singletons/time';
import { CoreDirectivesRegistry } from '@singletons/directives-registry';
import { CoreWSError } from '@classes/errors/wserror';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
/**
* Page that allows attempting a quiz.
@ -534,9 +535,7 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave {
// @todo MOBILE-4350: This is called before getting the attempt data in sequential quizzes as a workaround for a bug
// in the LMS. Once the bug has been fixed, this should be reverted.
if (this.isSequential) {
await CoreUtils.ignoreErrors(
AddonModQuiz.logViewAttempt(this.attempt.id, page, this.preflightData, this.offline, this.quiz),
);
await this.logViewPage(page);
}
const data = await AddonModQuiz.getAttemptData(this.attempt.id, page, this.preflightData, {
@ -569,15 +568,55 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave {
// Mark the page as viewed.
if (!this.isSequential) {
// @todo MOBILE-4350: Undo workaround.
CoreUtils.ignoreErrors(
AddonModQuiz.logViewAttempt(this.attempt.id, page, this.preflightData, this.offline, this.quiz),
);
await this.logViewPage(page);
}
// Start looking for changes.
this.autoSave.startCheckChangesProcess(this.quiz, this.attempt, this.preflightData, this.offline);
}
/**
* Log view a page.
*
* @param page Page viewed.
*/
protected async logViewPage(page: number): Promise<void> {
if (!this.quiz || !this.attempt) {
return;
}
await CoreUtils.ignoreErrors(AddonModQuiz.logViewAttempt(this.attempt.id, page, this.preflightData, this.offline));
CoreAnalytics.logEvent({
type: CoreAnalyticsEventType.VIEW_ITEM,
ws: 'mod_quiz_view_attempt',
name: this.quiz.name,
data: { id: this.attempt.id, quizid: this.quiz.id, page, category: 'quiz' },
url: `/mod/quiz/attempt.php?attempt=${this.attempt.id}&cmid=${this.cmId}` + (page > 0 ? `&page=${page}` : ''),
});
}
/**
* Log view summary.
*/
protected async logViewSummary(): Promise<void> {
if (!this.quiz || !this.attempt) {
return;
}
await CoreUtils.ignoreErrors(
AddonModQuiz.logViewAttemptSummary(this.attempt.id, this.preflightData, this.quiz.id),
);
CoreAnalytics.logEvent({
type: CoreAnalyticsEventType.VIEW_ITEM,
ws: 'mod_quiz_view_attempt_summary',
name: this.quiz.name,
data: { id: this.attempt.id, quizid: this.quiz.id, category: 'quiz' },
url: `/mod/quiz/summary.php?attempt=${this.attempt.id}&cmid=${this.cmId}`,
});
}
/**
* Refresh attempt data.
*/
@ -618,10 +657,7 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave {
this.dueDateWarning = AddonModQuiz.getAttemptDueDateWarning(this.quiz, this.attempt);
// Log summary as viewed.
CoreUtils.ignoreErrors(
AddonModQuiz.logViewAttemptSummary(this.attempt.id, this.preflightData, this.quiz.id, this.quiz.name),
);
this.logViewSummary();
}
/**

View File

@ -37,6 +37,7 @@ import {
AddonModQuizWSAdditionalData,
} from '../../services/quiz';
import { AddonModQuizHelper } from '../../services/quiz-helper';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
/**
* Page that allows reviewing a quiz attempt.
@ -73,11 +74,15 @@ export class AddonModQuizReviewPage implements OnInit {
protected attemptId!: number; // The attempt being reviewed.
protected currentPage!: number; // The current page being reviewed.
protected options?: AddonModQuizCombinedReviewOptions; // Review options.
protected fetchSuccess = false;
protected logView: () => void;
constructor(
protected elementRef: ElementRef,
) {
this.logView = CoreTime.once(() => this.performLogView(true, {
showAllDisabled: !this.showAll,
page: this.currentPage,
}));
}
/**
@ -127,6 +132,8 @@ export class AddonModQuizReviewPage implements OnInit {
try {
await this.loadPage(page);
this.performLogView(false, { page });
} catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'addon.mod_quiz.errorgetquestions', true);
} finally {
@ -156,12 +163,7 @@ export class AddonModQuizReviewPage implements OnInit {
// Load questions.
await this.loadPage(this.currentPage);
if (!this.fetchSuccess) {
this.fetchSuccess = true;
CoreUtils.ignoreErrors(
AddonModQuiz.logViewAttemptReview(this.attemptId, this.quiz.id, this.quiz.name),
);
}
this.logView();
} catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'addon.mod_quiz.errorgetquiz', true);
}
@ -325,11 +327,13 @@ export class AddonModQuizReviewPage implements OnInit {
/**
* Switch mode: all questions in same page OR one page at a time.
*/
switchMode(): void {
async switchMode(): Promise<void> {
this.showAll = !this.showAll;
// Load all questions or first page, depending on the mode.
this.loadPage(this.showAll ? -1 : 0);
await this.loadPage(this.showAll ? -1 : 0);
this.performLogView(false, { showAllDisabled: !this.showAll });
}
async openNavigation(): Promise<void> {
@ -351,6 +355,37 @@ export class AddonModQuizReviewPage implements OnInit {
this.changePage(modalData.page, modalData.slot);
}
/**
* Perform log view.
*
* @param logInLMS Whether to log in LMS too or only in analytics.
* @param options Other options.
*/
protected async performLogView(logInLMS = false, options: LogViewOptions = {}): Promise<void> {
if (!this.quiz) {
return;
}
if (logInLMS) {
await CoreUtils.ignoreErrors(AddonModQuiz.logViewAttemptReview(this.attemptId, this.quiz.id));
}
let url = `/mod/quiz/review.php?attempt=${this.attemptId}&cmid=${this.cmId}`;
if (options.showAllDisabled) {
url += '&showall=0';
} else if (options.page && options.page > 0) {
url += `&page=${ options.page}`;
}
CoreAnalytics.logEvent({
type: CoreAnalyticsEventType.VIEW_ITEM,
ws: 'mod_quiz_view_attempt_review',
name: this.quiz.name,
data: { id: this.attemptId, quizid: this.quiz.id, page: options.page, category: 'quiz' },
url: url,
});
}
}
/**
@ -359,3 +394,8 @@ export class AddonModQuizReviewPage implements OnInit {
type QuizQuestion = CoreQuestionQuestionParsed & {
readableMark?: string;
};
type LogViewOptions = {
page?: number; // Page being viewed (if viewing pages);
showAllDisabled?: boolean; // Whether the showAll option has just been disabled.
};

View File

@ -404,13 +404,11 @@ export class AddonModQuizSyncProvider extends CoreCourseActivitySyncBaseProvider
if (!finish) {
// Answers sent, now set the current page.
// Don't pass the quiz instance because we don't want to trigger a Firebase event in this case.
await CoreUtils.ignoreErrors(AddonModQuiz.logViewAttempt(
onlineAttempt.id,
offlineAttempt.currentpage,
preflightData,
false,
undefined,
siteId,
));
}

View File

@ -21,7 +21,6 @@ import { CoreSite, CoreSiteWSPreSets } from '@classes/site';
import { CoreCourseCommonModWSOptions } from '@features/course/services/course';
import { CoreCourseLogHelper } from '@features/course/services/log-helper';
import { CoreGradesFormattedItem, CoreGradesHelper } from '@features/grades/services/grades-helper';
import { CorePushNotifications } from '@features/pushnotifications/services/pushnotifications';
import {
CoreQuestion,
CoreQuestionQuestionParsed,
@ -1535,7 +1534,6 @@ export class AddonModQuizProvider {
* @param page Page number.
* @param preflightData Preflight required data (like password).
* @param offline Whether attempt is offline.
* @param quiz Quiz instance. If set, a Firebase event will be stored.
* @param siteId Site ID. If not defined, current site.
* @returns Promise resolved when the WS call is successful.
*/
@ -1544,7 +1542,6 @@ export class AddonModQuizProvider {
page: number = 0,
preflightData: Record<string, string> = {},
offline?: boolean,
quiz?: AddonModQuizQuizWSData,
siteId?: string,
): Promise<void> {
const site = await CoreSites.getSite(siteId);
@ -1564,16 +1561,6 @@ export class AddonModQuizProvider {
if (offline) {
promises.push(AddonModQuizOffline.setAttemptCurrentPage(attemptId, page, site.getId()));
}
if (quiz) {
CorePushNotifications.logViewEvent(
quiz.id,
quiz.name,
'quiz',
'mod_quiz_view_attempt',
{ attemptid: attemptId, page },
siteId,
);
}
await Promise.all(promises);
}
@ -1583,23 +1570,19 @@ export class AddonModQuizProvider {
*
* @param attemptId Attempt ID.
* @param quizId Quiz ID.
* @param name Name of the quiz.
* @param siteId Site ID. If not defined, current site.
* @returns Promise resolved when the WS call is successful.
*/
logViewAttemptReview(attemptId: number, quizId: number, name?: string, siteId?: string): Promise<void> {
logViewAttemptReview(attemptId: number, quizId: number, siteId?: string): Promise<void> {
const params: AddonModQuizViewAttemptReviewWSParams = {
attemptid: attemptId,
};
return CoreCourseLogHelper.logSingle(
return CoreCourseLogHelper.log(
'mod_quiz_view_attempt_review',
params,
AddonModQuizProvider.COMPONENT,
quizId,
name,
'quiz',
params,
siteId,
);
}
@ -1610,7 +1593,6 @@ export class AddonModQuizProvider {
* @param attemptId Attempt ID.
* @param preflightData Preflight required data (like password).
* @param quizId Quiz ID.
* @param name Name of the quiz.
* @param siteId Site ID. If not defined, current site.
* @returns Promise resolved when the WS call is successful.
*/
@ -1618,7 +1600,6 @@ export class AddonModQuizProvider {
attemptId: number,
preflightData: Record<string, string>,
quizId: number,
name?: string,
siteId?: string,
): Promise<void> {
const params: AddonModQuizViewAttemptSummaryWSParams = {
@ -1630,14 +1611,11 @@ export class AddonModQuizProvider {
),
};
return CoreCourseLogHelper.logSingle(
return CoreCourseLogHelper.log(
'mod_quiz_view_attempt_summary',
params,
AddonModQuizProvider.COMPONENT,
quizId,
name,
'quiz',
{ attemptid: attemptId },
siteId,
);
}
@ -1646,23 +1624,19 @@ export class AddonModQuizProvider {
* Report a quiz as being viewed.
*
* @param id Module ID.
* @param name Name of the quiz.
* @param siteId Site ID. If not defined, current site.
* @returns Promise resolved when the WS call is successful.
*/
logViewQuiz(id: number, name?: string, siteId?: string): Promise<void> {
logViewQuiz(id: number, siteId?: string): Promise<void> {
const params: AddonModQuizViewQuizWSParams = {
quizid: id,
};
return CoreCourseLogHelper.logSingle(
return CoreCourseLogHelper.log(
'mod_quiz_view_quiz',
params,
AddonModQuizProvider.COMPONENT,
id,
name,
'quiz',
{},
siteId,
);
}

View File

@ -47,6 +47,7 @@ import { CorePlatform } from '@services/platform';
export class AddonModResourceIndexComponent extends CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy {
component = AddonModResourceProvider.COMPONENT;
pluginName = 'resource';
mode = '';
src = '';
@ -188,7 +189,9 @@ export class AddonModResourceIndexComponent extends CoreCourseModuleMainResource
* @inheritdoc
*/
protected async logActivity(): Promise<void> {
await AddonModResource.logView(this.module.instance, this.module.name);
await CoreUtils.ignoreErrors(AddonModResource.logView(this.module.instance));
this.analyticsLogEvent('mod_resource_view_resource');
}
/**

View File

@ -28,6 +28,7 @@ import { CoreUtilsOpenFileOptions } from '@services/utils/utils';
import { makeSingleton, Translate } from '@singletons';
import { CorePath } from '@singletons/path';
import { AddonModResource, AddonModResourceProvider } from './resource';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
/**
* Service that provides helper functions for resources.
@ -206,6 +207,14 @@ export class AddonModResourceHelperProvider {
} catch {
// Ignore errors.
}
CoreAnalytics.logEvent({
type: CoreAnalyticsEventType.VIEW_ITEM,
ws: 'mod_resource_view_resource',
name: module.name,
data: { id: module.instance, category: 'resource' },
url: `/mod/resource/view.php?id=${module.id}`,
});
} catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'addon.mod_resource.errorwhileloadingthecontent', true);
} finally {

View File

@ -146,23 +146,19 @@ export class AddonModResourceProvider {
* Report the resource as being viewed.
*
* @param id Module ID.
* @param name Name of the resource.
* @param siteId Site ID. If not defined, current site.
* @returns Promise resolved when the WS call is successful.
*/
async logView(id: number, name?: string, siteId?: string): Promise<void> {
async logView(id: number, siteId?: string): Promise<void> {
const params: AddonModResourceViewResourceWSParams = {
resourceid: id,
};
await CoreCourseLogHelper.logSingle(
await CoreCourseLogHelper.log(
'mod_resource_view_resource',
params,
AddonModResourceProvider.COMPONENT,
id,
name,
'resource',
{},
siteId,
);
}

View File

@ -57,7 +57,7 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom
@Input() autoPlayData?: AddonModScormAutoPlayData; // Data to use to play the SCORM automatically.
component = AddonModScormProvider.COMPONENT;
moduleName = 'scorm';
pluginName = 'scorm';
scorm?: AddonModScormScorm; // The SCORM object.
currentOrganization: Partial<AddonModScormOrganization> & { identifier: string} = {
@ -361,7 +361,9 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom
return; // Shouldn't happen.
}
await AddonModScorm.logView(this.scorm.id, this.scorm.name);
await CoreUtils.ignoreErrors(AddonModScorm.logView(this.scorm.id));
this.analyticsLogEvent('mod_scorm_view_scorm');
}
/**

View File

@ -35,6 +35,7 @@ import {
} from '../../services/scorm';
import { AddonModScormHelper, AddonModScormTOCScoWithIcon } from '../../services/scorm-helper';
import { AddonModScormSync } from '../../services/scorm-sync';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
/**
* Page that allows playing a SCORM.
@ -442,8 +443,7 @@ export class AddonModScormPlayerPage implements OnInit, OnDestroy {
this.markCompleted(sco);
}
// Trigger SCO launch event.
CoreUtils.ignoreErrors(AddonModScorm.logLaunchSco(this.scorm.id, sco.id, this.scorm.name));
this.logEvent(sco.id);
}
/**
@ -581,6 +581,27 @@ export class AddonModScormPlayerPage implements OnInit, OnDestroy {
}));
}
/**
* Log event.
*/
protected async logEvent(scoId: number): Promise<void> {
await CoreUtils.ignoreErrors(AddonModScorm.logLaunchSco(this.scorm.id, scoId));
let url = '/mod/scorm/player.php';
if (this.scorm.popup) {
url += `?a=${this.scorm.id}&currentorg=${this.organizationId}&scoid=${scoId}` +
`&display=popup&mode=${this.mode}`;
}
CoreAnalytics.logEvent({
type: CoreAnalyticsEventType.VIEW_ITEM,
ws: 'mod_scorm_get_scorm_user_data',
name: this.scorm.name,
data: { id: this.scorm.id, scoid: scoId, organization: this.organizationId, category: 'scorm' },
url,
});
}
/**
* @inheritdoc
*/

View File

@ -1420,24 +1420,20 @@ export class AddonModScormProvider {
*
* @param scormId SCORM ID.
* @param scoId SCO ID.
* @param name Name of the SCORM.
* @param siteId Site ID. If not defined, current site.
* @returns Promise resolved when the WS call is successful.
*/
logLaunchSco(scormId: number, scoId: number, name?: string, siteId?: string): Promise<void> {
logLaunchSco(scormId: number, scoId: number, siteId?: string): Promise<void> {
const params: AddonModScormLaunchScoWSParams = {
scormid: scormId,
scoid: scoId,
};
return CoreCourseLogHelper.logSingle(
return CoreCourseLogHelper.log(
'mod_scorm_launch_sco',
params,
AddonModScormProvider.COMPONENT,
scormId,
name,
'scorm',
{ scoid: scoId },
siteId,
);
}
@ -1446,23 +1442,19 @@ export class AddonModScormProvider {
* Report a SCORM as being viewed.
*
* @param id Module ID.
* @param name Name of the SCORM.
* @param siteId Site ID. If not defined, current site.
* @returns Promise resolved when the WS call is successful.
*/
logView(id: number, name?: string, siteId?: string): Promise<void> {
logView(id: number, siteId?: string): Promise<void> {
const params: AddonModScormViewScormWSParams = {
scormid: id,
};
return CoreCourseLogHelper.logSingle(
return CoreCourseLogHelper.log(
'mod_scorm_view_scorm',
params,
AddonModScormProvider.COMPONENT,
id,
name,
'scorm',
{},
siteId,
);
}

View File

@ -38,6 +38,7 @@ import {
AddonModSurveySyncProvider,
AddonModSurveySyncResult,
} from '../../services/survey-sync';
import { CoreUtils } from '@services/utils/utils';
/**
* Component that displays a survey.
@ -50,7 +51,7 @@ import {
export class AddonModSurveyIndexComponent extends CoreCourseModuleMainActivityComponent implements OnInit {
component = AddonModSurveyProvider.COMPONENT;
moduleName = 'survey';
pluginName = 'survey';
survey?: AddonModSurveySurvey;
questions: AddonModSurveyQuestionFormatted[] = [];
@ -168,7 +169,9 @@ export class AddonModSurveyIndexComponent extends CoreCourseModuleMainActivityCo
return; // Shouldn't happen.
}
await AddonModSurvey.logView(this.survey.id, this.survey.name);
await CoreUtils.ignoreErrors(AddonModSurvey.logView(this.survey.id));
this.analyticsLogEvent('mod_survey_view_survey');
}
/**

View File

@ -208,7 +208,6 @@ export class AddonModSurveyProvider {
* Report the survey as being viewed.
*
* @param id Module ID.
* @param name Name of the assign.
* @param siteId Site ID. If not defined, current site.
* @returns Promise resolved when the WS call is successful.
*/
@ -217,14 +216,11 @@ export class AddonModSurveyProvider {
surveyid: id,
};
await CoreCourseLogHelper.logSingle(
await CoreCourseLogHelper.log(
'mod_survey_view_survey',
params,
AddonModSurveyProvider.COMPONENT,
id,
name,
'survey',
{},
siteId,
);
}

View File

@ -34,6 +34,7 @@ import { AddonModUrlHelper } from '../../services/url-helper';
export class AddonModUrlIndexComponent extends CoreCourseModuleMainResourceComponent implements OnInit {
component = AddonModUrlProvider.COMPONENT;
pluginName = 'url';
url?: string;
name?: string;
@ -153,12 +154,14 @@ export class AddonModUrlIndexComponent extends CoreCourseModuleMainResourceCompo
*/
protected async logView(): Promise<void> {
try {
await AddonModUrl.logView(this.module.instance, this.module.name);
await AddonModUrl.logView(this.module.instance);
this.checkCompletion();
} catch {
// Ignore errors.
}
this.analyticsLogEvent('mod_url_view_url');
}
/**

View File

@ -26,6 +26,7 @@ import { makeSingleton } from '@singletons';
import { AddonModUrlIndexComponent } from '../../components/index/index';
import { AddonModUrl } from '../url';
import { AddonModUrlHelper } from '../url-helper';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
/**
* Handler to support url modules.
@ -64,14 +65,7 @@ export class AddonModUrlModuleHandlerService extends CoreModuleHandlerBase imple
* @param courseId The course ID.
*/
const openUrl = async (module: CoreCourseModuleData, courseId: number): Promise<void> => {
try {
if (module.instance) {
await AddonModUrl.logView(module.instance, module.name);
CoreCourse.checkModuleCompletion(module.course, module.completiondata);
}
} catch {
// Ignore errors.
}
await this.logView(module);
CoreCourse.storeModuleViewed(courseId, module.id);
@ -196,5 +190,27 @@ export class AddonModUrlModuleHandlerService extends CoreModuleHandlerBase imple
return !iconUrl?.startsWith('assets/img/files/');
}
/**
* Log module viewed.
*/
protected async logView(module: CoreCourseModuleData): Promise<void> {
try {
if (module.instance) {
await AddonModUrl.logView(module.instance);
CoreCourse.checkModuleCompletion(module.course, module.completiondata);
}
} catch {
// Ignore errors.
}
CoreAnalytics.logEvent({
type: CoreAnalyticsEventType.VIEW_ITEM,
ws: 'mod_url_view_url',
name: module.name,
data: { id: module.instance, category: 'url' },
url: `/mod/url/view.php?id=${module.id}`,
});
}
}
export const AddonModUrlModuleHandler = makeSingleton(AddonModUrlModuleHandlerService);

View File

@ -210,23 +210,19 @@ export class AddonModUrlProvider {
* Report the url as being viewed.
*
* @param id Module ID.
* @param name Name of the assign.
* @param siteId Site ID. If not defined, current site.
* @returns Promise resolved when the WS call is successful.
*/
logView(id: number, name?: string, siteId?: string): Promise<void> {
logView(id: number, siteId?: string): Promise<void> {
const params: AddonModUrlViewUrlWSParams = {
urlid: id,
};
return CoreCourseLogHelper.logSingle(
return CoreCourseLogHelper.log(
'mod_url_view_url',
params,
AddonModUrlProvider.COMPONENT,
id,
name,
'url',
{},
siteId,
);
}

View File

@ -75,7 +75,7 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp
component = AddonModWikiProvider.COMPONENT;
componentId?: number;
moduleName = 'wiki';
pluginName = 'wiki';
groupWiki = false;
isOnline = false;
@ -327,9 +327,7 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp
await this.showLoadingAndFetch(true, false);
if (this.currentPage && this.wiki) {
CoreUtils.ignoreErrors(AddonModWiki.logPageView(this.currentPage, this.wiki.id, this.wiki.name));
}
this.currentPage && this.logPageViewed(this.currentPage);
}, CoreSites.getCurrentSiteId());
}
@ -443,12 +441,60 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp
return; // Shouldn't happen.
}
if (!this.pageId) {
await AddonModWiki.logView(this.wiki.id, this.wiki.name);
} else {
if (this.pageId) {
// View page.
this.checkCompletionAfterLog = false;
CoreUtils.ignoreErrors(AddonModWiki.logPageView(this.pageId, this.wiki.id, this.wiki.name));
await this.logPageViewed(this.pageId);
return;
}
await AddonModWiki.logView(this.wiki.id);
if (this.groupId === undefined && this.userId === undefined) {
// View initial page.
this.analyticsLogEvent('mod_wiki_view_wiki', { name: this.currentPageObj?.title });
return;
}
// Viewing a different subwiki.
const hasPersonalSubwikis = this.loadedSubwikis.some(subwiki => subwiki.userid > 0);
const hasGroupSubwikis = this.loadedSubwikis.some(subwiki => subwiki.groupid > 0);
let url = `/mod/wiki/view.php?wid=${this.wiki.id}&title=${this.wiki.firstpagetitle}`;
if (hasPersonalSubwikis && hasGroupSubwikis) {
url += `&groupanduser=${this.groupId}-${this.userId}`;
} else if (hasPersonalSubwikis) {
url += `&uid=${this.userId}`;
} else {
url += `&group=${this.groupId}`;
}
this.analyticsLogEvent('mod_wiki_view_wiki', {
name: this.currentPageObj?.title,
data: { subwiki: this.subwikiId, userid: this.userId, groupid: this.groupId },
url,
});
}
/**
* Log page viewed.
*
* @param pageId Page ID.
*/
protected async logPageViewed(pageId: number): Promise<void> {
if (!this.wiki) {
return; // Shouldn't happen.
}
await CoreUtils.ignoreErrors(AddonModWiki.logPageView(pageId, this.wiki.id));
this.analyticsLogEvent('mod_wiki_view_page', {
name: this.currentPageObj?.title,
data: { pageid: this.pageId },
url: `/mod/wiki/view.php?page=${this.pageId}`,
});
}
/**
@ -619,7 +665,9 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp
homeView: this.getWikiHomeView(),
moduleId: this.module.id,
courseId: this.courseId,
selectedId: this.currentPage,
selectedTitle: this.currentPageObj && this.currentPageObj.title,
wiki: this.wiki,
},
});

View File

@ -15,7 +15,8 @@
import { Component, Input, OnInit } from '@angular/core';
import { ModalController } from '@singletons';
import { AddonModWikiPageDBRecord } from '../../services/database/wiki';
import { AddonModWikiSubwikiPage } from '../../services/wiki';
import { AddonModWikiSubwikiPage, AddonModWikiWiki } from '../../services/wiki';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
/**
* Modal to display the map of a Wiki.
@ -27,6 +28,8 @@ import { AddonModWikiSubwikiPage } from '../../services/wiki';
export class AddonModWikiMapModalComponent implements OnInit {
@Input() pages: (AddonModWikiSubwikiPage | AddonModWikiPageDBRecord)[] = [];
@Input() wiki?: AddonModWikiWiki;
@Input() selectedId?: number;
@Input() selectedTitle?: string;
@Input() moduleId?: number;
@Input() courseId?: number;
@ -39,6 +42,16 @@ export class AddonModWikiMapModalComponent implements OnInit {
*/
ngOnInit(): void {
this.constructMap();
if (this.selectedId && this.wiki) {
CoreAnalytics.logEvent({
type: CoreAnalyticsEventType.VIEW_ITEM,
ws: 'mod_wiki_get_subwiki_pages',
name: this.selectedTitle || this.wiki.name,
data: { id: this.wiki.id, pageid: this.selectedId, category: 'wiki' },
url: `/mod/wiki/map.php?pageid=${this.selectedId}`,
});
}
}
/**

View File

@ -30,6 +30,7 @@ import { CoreForms } from '@singletons/form';
import { AddonModWiki, AddonModWikiProvider } from '../../services/wiki';
import { AddonModWikiOffline } from '../../services/wiki-offline';
import { AddonModWikiSync } from '../../services/wiki-sync';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
/**
* Page that allows adding or editing a wiki page.
@ -64,6 +65,7 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy, CanLeave {
protected editOffline = false; // Whether the user is editing an offline page.
protected subwikiFiles: CoreWSFile[] = []; // List of files of the subwiki.
protected originalContent?: string; // The original page content.
protected originalTitle?: string; // The original page title.
protected version?: number; // Page version.
protected renewLockInterval?: number; // An interval to renew the lock every certain time.
protected forceLeave = false; // To allow leaving the page without checking for changes.
@ -89,17 +91,17 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy, CanLeave {
this.groupId = CoreNavigator.getRouteNumberParam('groupId');
this.userId = CoreNavigator.getRouteNumberParam('userId');
let pageTitle = CoreNavigator.getRouteParam<string>('pageTitle');
pageTitle = pageTitle ? CoreTextUtils.cleanTags(pageTitle.replace(/\+/g, ' '), { singleLine: true }) : '';
const pageTitle = CoreNavigator.getRouteParam<string>('pageTitle');
this.originalTitle = pageTitle ? CoreTextUtils.cleanTags(pageTitle.replace(/\+/g, ' '), { singleLine: true }) : '';
this.canEditTitle = !pageTitle;
this.title = pageTitle ?
Translate.instant('addon.mod_wiki.editingpage', { $a: pageTitle }) :
this.canEditTitle = !this.originalTitle;
this.title = this.originalTitle ?
Translate.instant('addon.mod_wiki.editingpage', { $a: this.originalTitle }) :
Translate.instant('addon.mod_wiki.newpagehdr');
this.blockId = AddonModWikiSync.getSubwikiBlockId(this.subwikiId, this.wikiId, this.userId, this.groupId);
// Create the form group and its controls.
this.pageForm.addControl('title', this.formBuilder.control(pageTitle));
this.pageForm.addControl('title', this.formBuilder.control(this.originalTitle));
this.pageForm.addControl('text', this.contentControl);
// Block the wiki so it cannot be synced.
@ -111,8 +113,8 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy, CanLeave {
if (this.section) {
this.editorExtraParams.section = this.section;
}
} else if (pageTitle) {
this.editorExtraParams.pagetitle = pageTitle;
} else if (this.originalTitle) {
this.editorExtraParams.pagetitle = this.originalTitle;
}
try {
@ -126,6 +128,8 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy, CanLeave {
this.blockId = newBlockId;
CoreSync.blockOperation(this.component, this.blockId);
}
this.logView();
}
} finally {
this.loaded = true;
@ -158,6 +162,7 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy, CanLeave {
this.wikiId = pageContents.wikiid;
this.subwikiId = pageContents.subwikiid;
this.title = Translate.instant('addon.mod_wiki.editingpage', { $a: pageContents.title });
this.originalTitle = pageContents.title;
this.groupId = pageContents.groupid;
this.userId = pageContents.userid;
canEdit = pageContents.caneditpage;
@ -466,6 +471,34 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy, CanLeave {
}
}
/**
* Log view.
*/
protected logView(): void {
let url: string;
if (this.pageId) {
url = `/mod/wiki/edit.php?pageid=${this.pageId}` +
(this.section ? `&section=${this.section.replace(/ /g, '+')}` : '');
} else if (this.originalTitle) {
const title = this.originalTitle.replace(/ /g, '+');
if (this.subwikiId) {
url = `/mod/wiki/create.php?swid=${this.subwikiId}&title=${title}&action=new`;
} else {
url = `/mod/wiki/create.php?wid=${this.wikiId}&group=${this.groupId ?? 0}&uid=${this.userId ?? 0}&title=${title}`;
}
} else {
url = `/mod/wiki/create.php?action=new&wid=${this.wikiId}&swid=${this.subwikiId}`;
}
CoreAnalytics.logEvent({
type: CoreAnalyticsEventType.VIEW_ITEM,
ws: this.pageId ? 'mod_wiki_edit_page' : 'mod_wiki_new_page',
name: this.originalTitle ?? Translate.instant('addon.mod_wiki.newpagehdr'),
data: { id: this.wikiId, subwiki: this.subwikiId, category: 'wiki' },
url,
});
}
/**
* @inheritdoc
*/

View File

@ -621,23 +621,19 @@ export class AddonModWikiProvider {
*
* @param id Page ID.
* @param wikiId Wiki ID.
* @param name Name of the wiki.
* @param siteId Site ID. If not defined, current site.
* @returns Promise resolved when the WS call is successful.
*/
logPageView(id: number, wikiId: number, name?: string, siteId?: string): Promise<void> {
logPageView(id: number, wikiId: number, siteId?: string): Promise<void> {
const params: AddonModWikiViewPageWSParams = {
pageid: id,
};
return CoreCourseLogHelper.logSingle(
return CoreCourseLogHelper.log(
'mod_wiki_view_page',
params,
AddonModWikiProvider.COMPONENT,
wikiId,
name,
'wiki',
params,
siteId,
);
}
@ -646,23 +642,19 @@ export class AddonModWikiProvider {
* Report the wiki as being viewed.
*
* @param id Wiki ID.
* @param name Name of the wiki.
* @param siteId Site ID. If not defined, current site.
* @returns Promise resolved when the WS call is successful.
*/
logView(id: number, name?: string, siteId?: string): Promise<void> {
logView(id: number, siteId?: string): Promise<void> {
const params: AddonModWikiViewWikiWSParams = {
wikiid: id,
};
return CoreCourseLogHelper.logSingle(
return CoreCourseLogHelper.log(
'mod_wiki_view_wiki',
params,
AddonModWikiProvider.COMPONENT,
id,
name,
'wiki',
{},
siteId,
);
}

View File

@ -66,7 +66,7 @@ export class AddonModWorkshopIndexComponent extends CoreCourseModuleMainActivity
@Input() group = 0;
component = AddonModWorkshopProvider.COMPONENT;
moduleName = 'workshop';
pluginName = 'workshop';
workshop?: AddonModWorkshopData;
page = 0;
@ -255,7 +255,9 @@ export class AddonModWorkshopIndexComponent extends CoreCourseModuleMainActivity
return; // Shouldn't happen.
}
await AddonModWorkshop.logView(this.workshop.id, this.workshop.name);
await CoreUtils.ignoreErrors(AddonModWorkshop.logView(this.workshop.id));
this.analyticsLogEvent('mod_workshop_view_workshop');
}
/**

View File

@ -39,6 +39,8 @@ import {
import { AddonModWorkshopHelper, AddonModWorkshopSubmissionAssessmentWithFormData } from '../../services/workshop-helper';
import { AddonModWorkshopOffline } from '../../services/workshop-offline';
import { AddonModWorkshopSyncProvider } from '../../services/workshop-sync';
import { CoreTime } from '@singletons/time';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
/**
* Page that displays a workshop assessment.
@ -89,6 +91,7 @@ export class AddonModWorkshopAssessmentPage implements OnInit, OnDestroy, CanLea
protected siteId: string;
protected currentUserId: number;
protected forceLeave = false;
protected logView: () => void;
constructor(
protected fb: FormBuilder,
@ -111,6 +114,20 @@ export class AddonModWorkshopAssessmentPage implements OnInit, OnDestroy, CanLea
this.refreshAllData();
}
}, this.siteId);
this.logView = CoreTime.once(async () => {
if (!this.workshop) {
return;
}
CoreAnalytics.logEvent({
type: CoreAnalyticsEventType.VIEW_ITEM,
ws: 'mod_workshop_get_assessment',
name: this.workshop.name,
data: { id: this.workshop.id, assessmentid: this.assessment.id, category: 'workshop' },
url: `/mod/workshop/assessment.php?asid=${this.assessment.id}`,
});
});
}
/**

View File

@ -40,6 +40,7 @@ import {
} from '../../services/workshop';
import { AddonModWorkshopHelper, AddonModWorkshopSubmissionDataWithOfflineData } from '../../services/workshop-helper';
import { AddonModWorkshopOffline } from '../../services/workshop-offline';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
/**
* Page that displays the workshop edit submission.
@ -224,6 +225,8 @@ export class AddonModWorkshopEditSubmissionPage implements OnInit, OnDestroy, Ca
}
this.loaded = true;
this.logView();
} catch (error) {
this.loaded = false;
@ -233,6 +236,23 @@ export class AddonModWorkshopEditSubmissionPage implements OnInit, OnDestroy, Ca
}
}
/**
* Log view.
*/
protected logView(): void {
if (!this.workshop) {
return;
}
CoreAnalytics.logEvent({
type: CoreAnalyticsEventType.VIEW_ITEM,
ws: this.editing ? 'mod_workshop_update_submission' : 'mod_workshop_add_submission',
name: this.workshop.name,
data: { id: this.workshop.id, submissionid: this.submissionId, category: 'workshop' },
url: `/mod/workshop/submission.php?cmid=${this.module.id}&id=${this.submissionId}&edit=on`,
});
}
/**
* Force leaving the page, without checking for changes.
*/

View File

@ -47,6 +47,8 @@ import {
} from '../../services/workshop-helper';
import { AddonModWorkshopOffline } from '../../services/workshop-offline';
import { AddonModWorkshopSyncProvider, AddonModWorkshopAutoSyncData } from '../../services/workshop-sync';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
import { CoreTime } from '@singletons/time';
/**
* Page that displays a workshop submission.
@ -102,7 +104,7 @@ export class AddonModWorkshopSubmissionPage implements OnInit, OnDestroy, CanLea
protected obsAssessmentSaved: CoreEventObserver;
protected syncObserver: CoreEventObserver;
protected isDestroyed = false;
protected fetchSuccess = false;
protected logView: () => void;
constructor(
protected fb: FormBuilder,
@ -125,6 +127,8 @@ export class AddonModWorkshopSubmissionPage implements OnInit, OnDestroy, CanLea
// Update just when all database is synced.
this.eventReceived(data);
}, this.siteId);
this.logView = CoreTime.once(() => this.performLogView());
}
/**
@ -599,19 +603,21 @@ export class AddonModWorkshopSubmissionPage implements OnInit, OnDestroy, CanLea
/**
* Log submission viewed.
*/
protected async logView(): Promise<void> {
if (this.fetchSuccess) {
return; // Already done.
}
this.fetchSuccess = true;
protected async performLogView(): Promise<void> {
try {
await AddonModWorkshop.logViewSubmission(this.submissionId, this.workshopId, this.workshop.name);
await AddonModWorkshop.logViewSubmission(this.submissionId, this.workshopId);
CoreCourse.checkModuleCompletion(this.courseId, this.module.completiondata);
} catch {
// Ignore errors.
}
CoreAnalytics.logEvent({
type: CoreAnalyticsEventType.VIEW_ITEM,
ws: 'mod_workshop_view_submission',
name: this.workshop.name,
data: { id: this.workshop.id, submissionid: this.submissionId, category: 'workshop' },
url: `/mod/workshop/submission.php?cmid=${this.module.id}&id=${this.submissionId}`,
});
}
/**

View File

@ -1443,23 +1443,19 @@ export class AddonModWorkshopProvider {
* Report the workshop as being viewed.
*
* @param id Workshop ID.
* @param name Name of the workshop.
* @param siteId Site ID. If not defined, current site.
* @returns Promise resolved when the WS call is successful.
*/
async logView(id: number, name?: string, siteId?: string): Promise<void> {
async logView(id: number, siteId?: string): Promise<void> {
const params: AddonModWorkshopViewWorkshopWSParams = {
workshopid: id,
};
await CoreCourseLogHelper.logSingle(
await CoreCourseLogHelper.log(
'mod_workshop_view_workshop',
params,
AddonModWorkshopProvider.COMPONENT,
id,
name,
'workshop',
{},
siteId,
);
}
@ -1469,23 +1465,19 @@ export class AddonModWorkshopProvider {
*
* @param id Submission ID.
* @param workshopId Workshop ID.
* @param name Name of the workshop.
* @param siteId Site ID. If not defined, current site.
* @returns Promise resolved when the WS call is successful.
*/
async logViewSubmission(id: number, workshopId: number, name?: string, siteId?: string): Promise<void> {
async logViewSubmission(id: number, workshopId: number, siteId?: string): Promise<void> {
const params: AddonModWorkshopViewSubmissionWSParams = {
submissionid: id,
};
await CoreCourseLogHelper.logSingle(
await CoreCourseLogHelper.log(
'mod_workshop_view_submission',
params,
AddonModWorkshopProvider.COMPONENT,
workshopId,
name,
'workshop',
params,
siteId,
);
}

View File

@ -21,12 +21,16 @@ import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { CoreAnimations } from '@components/animations';
import { CoreUser, CoreUserProfile } from '@features/user/services/user';
import { IonContent, IonRefresher } from '@ionic/angular';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
import { CoreNavigator } from '@services/navigator';
import { CoreSites } from '@services/sites';
import { CoreDomUtils, ToastDuration } from '@services/utils/dom';
import { CoreTextUtils } from '@services/utils/text';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreUtils } from '@services/utils/utils';
import { Translate } from '@singletons';
import { CoreEventObserver, CoreEvents } from '@singletons/events';
import { CoreTime } from '@singletons/time';
/**
* Page that displays a list of notes.
@ -54,9 +58,11 @@ export class AddonNotesListPage implements OnInit, OnDestroy {
currentUserId!: number;
protected syncObserver!: CoreEventObserver;
protected logAfterFetch = true;
protected logView: () => void;
constructor() {
this.logView = CoreTime.once(() => this.performLogView());
try {
this.courseId = CoreNavigator.getRequiredRouteNumberParam('courseId');
this.userId = CoreNavigator.getRouteNumberParam('userId');
@ -128,10 +134,7 @@ export class AddonNotesListPage implements OnInit, OnDestroy {
this.notes = await AddonNotes.getNotesUserData(notesList);
}
if (this.logAfterFetch) {
this.logAfterFetch = false;
CoreUtils.ignoreErrors(AddonNotes.logView(this.courseId, this.userId));
}
this.logView();
} catch (error) {
CoreDomUtils.showErrorModal(error);
} finally {
@ -176,7 +179,6 @@ export class AddonNotesListPage implements OnInit, OnDestroy {
this.notesLoaded = false;
this.refreshIcon = CoreConstants.ICON_LOADING;
this.syncIcon = CoreConstants.ICON_LOADING;
this.logAfterFetch = true;
await this.fetchNotes(true);
}
@ -190,6 +192,8 @@ export class AddonNotesListPage implements OnInit, OnDestroy {
e.preventDefault();
e.stopPropagation();
this.logViewAdd();
const modalData = await CoreDomUtils.openModal<AddonNotesAddModalReturn>({
component: AddonNotesAddComponent,
componentProps: {
@ -225,6 +229,8 @@ export class AddonNotesListPage implements OnInit, OnDestroy {
e.stopPropagation();
try {
this.logViewDelete(note);
await CoreDomUtils.showDeleteConfirm('addon.notes.deleteconfirm');
try {
await AddonNotes.deleteNote(note, this.courseId);
@ -294,6 +300,58 @@ export class AddonNotesListPage implements OnInit, OnDestroy {
}
}
/**
* Log view.
*/
protected async performLogView(): Promise<void> {
await CoreUtils.ignoreErrors(AddonNotes.logView(this.courseId, this.userId));
CoreAnalytics.logEvent({
type: CoreAnalyticsEventType.VIEW_ITEM_LIST,
ws: 'core_notes_view_notes',
name: Translate.instant('addon.notes.notes'),
data: { courseid: this.courseId, userid: this.userId || 0, category: 'notes' },
url: CoreUrlUtils.addParamsToUrl('/notes/index.php', {
user: this.userId,
course: this.courseId !== CoreSites.getCurrentSiteHomeId() ? this.courseId : undefined,
}),
});
}
/**
* Log view.
*/
protected async logViewAdd(): Promise<void> {
CoreAnalytics.logEvent({
type: CoreAnalyticsEventType.VIEW_ITEM,
ws: 'core_notes_create_notes',
name: Translate.instant('addon.notes.notes'),
data: { courseid: this.courseId, userid: this.userId || 0, category: 'notes' },
url: CoreUrlUtils.addParamsToUrl('/notes/edit.php', {
courseid: this.courseId,
userid: this.userId,
publishstate: this.type === 'personal' ? 'draft' : (this.type === 'course' ? 'public' : 'site'),
}),
});
}
/**
* Log view.
*/
protected async logViewDelete(note: AddonNotesNoteFormatted): Promise<void> {
if (!note.id) {
return;
}
CoreAnalytics.logEvent({
type: CoreAnalyticsEventType.VIEW_ITEM,
ws: 'core_notes_delete_notes',
name: Translate.instant('addon.notes.notes'),
data: { id: note.id, category: 'notes' },
url: `/notes/delete.php?id=${note.id}`,
});
}
/**
* Page destroyed.
*/

View File

@ -15,7 +15,6 @@
import { Injectable } from '@angular/core';
import { CoreWSError } from '@classes/errors/wserror';
import { CoreSite, CoreSiteWSPreSets } from '@classes/site';
import { CorePushNotifications } from '@features/pushnotifications/services/pushnotifications';
import { CoreUser } from '@features/user/services/user';
import { CoreNetwork } from '@services/network';
import { CoreSites } from '@services/sites';
@ -414,8 +413,6 @@ export class AddonNotesProvider {
userid: userId || 0,
};
CorePushNotifications.logViewListEvent('notes', 'core_notes_view_notes', params, site.getId());
await site.write('core_notes_view_notes', params);
}

Some files were not shown because too many files have changed in this diff Show More