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.currentmonth": "local_moodlemobileapp",
"addon.calendar.daynext": "calendar", "addon.calendar.daynext": "calendar",
"addon.calendar.dayprev": "calendar", "addon.calendar.dayprev": "calendar",
"addon.calendar.dayviewtitle": "calendar",
"addon.calendar.defaultnotificationtime": "local_moodlemobileapp", "addon.calendar.defaultnotificationtime": "local_moodlemobileapp",
"addon.calendar.deleteallevents": "calendar", "addon.calendar.deleteallevents": "calendar",
"addon.calendar.deleteevent": "calendar", "addon.calendar.deleteevent": "calendar",
"addon.calendar.deleteoneevent": "calendar", "addon.calendar.deleteoneevent": "calendar",
"addon.calendar.detailedmonthviewtitle": "calendar",
"addon.calendar.durationminutes": "calendar", "addon.calendar.durationminutes": "calendar",
"addon.calendar.durationnone": "calendar", "addon.calendar.durationnone": "calendar",
"addon.calendar.durationuntil": "calendar", "addon.calendar.durationuntil": "calendar",
@ -369,6 +371,7 @@
"addon.mod_assign.gradelocked": "assign", "addon.mod_assign.gradelocked": "assign",
"addon.mod_assign.gradenotsynced": "local_moodlemobileapp", "addon.mod_assign.gradenotsynced": "local_moodlemobileapp",
"addon.mod_assign.gradeoutof": "assign", "addon.mod_assign.gradeoutof": "assign",
"addon.mod_assign.grading": "assign",
"addon.mod_assign.gradingstatus": "assign", "addon.mod_assign.gradingstatus": "assign",
"addon.mod_assign.groupsubmissionsettings": "assign", "addon.mod_assign.groupsubmissionsettings": "assign",
"addon.mod_assign.hiddenuser": "assign", "addon.mod_assign.hiddenuser": "assign",
@ -425,6 +428,7 @@
"addon.mod_assign.submittedlate": "assign", "addon.mod_assign.submittedlate": "assign",
"addon.mod_assign.submittedovertime": "assign", "addon.mod_assign.submittedovertime": "assign",
"addon.mod_assign.submittedundertime": "assign", "addon.mod_assign.submittedundertime": "assign",
"addon.mod_assign.subpagetitle": "assign",
"addon.mod_assign.syncblockedusercomponent": "local_moodlemobileapp", "addon.mod_assign.syncblockedusercomponent": "local_moodlemobileapp",
"addon.mod_assign.timelimit": "assign", "addon.mod_assign.timelimit": "assign",
"addon.mod_assign.timemodified": "assign", "addon.mod_assign.timemodified": "assign",
@ -2235,6 +2239,7 @@
"core.play": "local_moodlemobileapp", "core.play": "local_moodlemobileapp",
"core.previous": "moodle", "core.previous": "moodle",
"core.proceed": "moodle", "core.proceed": "moodle",
"core.publicprofile": "moodle",
"core.pulltorefresh": "local_moodlemobileapp", "core.pulltorefresh": "local_moodlemobileapp",
"core.qrscanner": "local_moodlemobileapp", "core.qrscanner": "local_moodlemobileapp",
"core.question.answer": "question", "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 { CoreSwipeNavigationItemsManager } from '@classes/items-management/swipe-navigation-items-manager';
import { AddonBadgesUserBadgesSource } from '@addons/badges/classes/user-badges-source'; import { AddonBadgesUserBadgesSource } from '@addons/badges/classes/user-badges-source';
import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker'; 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. * Page that displays the list of calendar events.
@ -38,6 +40,7 @@ export class AddonBadgesIssuedBadgePage implements OnInit, OnDestroy {
protected badgeHash = ''; protected badgeHash = '';
protected userId!: number; protected userId!: number;
protected logView: (badge: AddonBadgesUserBadge) => void;
courseId = 0; courseId = 0;
user?: CoreUserProfile; user?: CoreUserProfile;
@ -58,6 +61,16 @@ export class AddonBadgesIssuedBadgePage implements OnInit, OnDestroy {
); );
this.badges = new CoreSwipeNavigationItemsManager(source); 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.course = undefined;
} }
} }
this.logView(badge);
} catch (message) { } catch (message) {
CoreDomUtils.showErrorModalDefault(message, 'Error getting badge data.'); 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 { CoreListItemsManager } from '@classes/items-management/list-items-manager';
import { AddonBadgesUserBadgesSource } from '@addons/badges/classes/user-badges-source'; import { AddonBadgesUserBadgesSource } from '@addons/badges/classes/user-badges-source';
import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker'; 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. * Page that displays the list of calendar events.
@ -39,6 +42,8 @@ export class AddonBadgesUserBadgesPage implements AfterViewInit, OnDestroy {
@ViewChild(CoreSplitViewComponent) splitView!: CoreSplitViewComponent; @ViewChild(CoreSplitViewComponent) splitView!: CoreSplitViewComponent;
protected logView: () => void;
constructor() { constructor() {
let courseId = CoreNavigator.getRouteNumberParam('courseId') ?? 0; // Use 0 for site badges. let courseId = CoreNavigator.getRouteNumberParam('courseId') ?? 0; // Use 0 for site badges.
const userId = CoreNavigator.getRouteNumberParam('userId') ?? CoreSites.getCurrentSiteUserId(); const userId = CoreNavigator.getRouteNumberParam('userId') ?? CoreSites.getCurrentSiteUserId();
@ -52,6 +57,16 @@ export class AddonBadgesUserBadgesPage implements AfterViewInit, OnDestroy {
CoreRoutedItemsManagerSourcesTracker.getOrCreateSource(AddonBadgesUserBadgesSource, [courseId, userId]), CoreRoutedItemsManagerSourcesTracker.getOrCreateSource(AddonBadgesUserBadgesSource, [courseId, userId]),
AddonBadgesUserBadgesPage, 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 { try {
await this.badges.reload(); await this.badges.reload();
this.logView();
} catch (message) { } catch (message) {
CoreDomUtils.showErrorModalDefault(message, 'Error loading badges'); 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 { CoreTag } from '@features/tag/services/tag';
import { CoreUser, CoreUserProfile } from '@features/user/services/user'; import { CoreUser, CoreUserProfile } from '@features/user/services/user';
import { IonRefresher } from '@ionic/angular'; import { IonRefresher } from '@ionic/angular';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
import { CoreNavigator } from '@services/navigator'; import { CoreNavigator } from '@services/navigator';
import { CoreSites } from '@services/sites'; import { CoreSites } from '@services/sites';
import { CoreDomUtils } from '@services/utils/dom'; import { CoreDomUtils } from '@services/utils/dom';
import { CoreTextUtils } from '@services/utils/text'; import { CoreTextUtils } from '@services/utils/text';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreUtils } from '@services/utils/utils'; import { CoreUtils } from '@services/utils/utils';
import { CoreTime } from '@singletons/time';
/** /**
* Page that displays the list of blog entries. * Page that displays the list of blog entries.
@ -43,7 +46,7 @@ export class AddonBlogEntriesPage implements OnInit {
protected canLoadMoreEntries = false; protected canLoadMoreEntries = false;
protected canLoadMoreUserEntries = true; protected canLoadMoreUserEntries = true;
protected siteHomeId: number; protected siteHomeId: number;
protected fetchSuccess = false; protected logView: () => void;
loaded = false; loaded = false;
canLoadMore = false; canLoadMore = false;
@ -61,6 +64,25 @@ export class AddonBlogEntriesPage implements OnInit {
constructor() { constructor() {
this.currentUserId = CoreSites.getCurrentSiteUserId(); this.currentUserId = CoreSites.getCurrentSiteUserId();
this.siteHomeId = CoreSites.getCurrentSiteHomeId(); 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); await Promise.all(promises);
if (!this.fetchSuccess) { this.logView();
this.fetchSuccess = true;
CoreUtils.ignoreErrors(AddonBlog.logView(this.filter));
}
} catch (error) { } catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'addon.blog.errorloadentries', true); CoreDomUtils.showErrorModalDefault(error, 'addon.blog.errorloadentries', true);
this.loadMoreError = true; // Set to prevent infinite calls with infinite-loading. this.loadMoreError = true; // Set to prevent infinite calls with infinite-loading.

View File

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

View File

@ -49,6 +49,10 @@ import {
} from '@classes/items-management/swipe-slides-dynamic-items-manager-source'; } from '@classes/items-management/swipe-slides-dynamic-items-manager-source';
import { CoreSwipeSlidesDynamicItemsManager } from '@classes/items-management/swipe-slides-dynamic-items-manager'; import { CoreSwipeSlidesDynamicItemsManager } from '@classes/items-management/swipe-slides-dynamic-items-manager';
import moment from 'moment-timezone'; 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. * Component that displays a calendar.
@ -81,6 +85,7 @@ export class AddonCalendarCalendarComponent implements OnInit, DoCheck, OnDestro
// Observers and listeners. // Observers and listeners.
protected undeleteEventObserver: CoreEventObserver; protected undeleteEventObserver: CoreEventObserver;
protected managerUnsubscribe?: () => void; protected managerUnsubscribe?: () => void;
protected logView: () => void;
constructor(differs: KeyValueDiffers) { constructor(differs: KeyValueDiffers) {
this.currentSiteId = CoreSites.getCurrentSiteId(); this.currentSiteId = CoreSites.getCurrentSiteId();
@ -107,6 +112,29 @@ export class AddonCalendarCalendarComponent implements OnInit, DoCheck, OnDestro
this.hiddenDiffer = this.hidden; this.hiddenDiffer = this.hidden;
this.filterDiffer = differs.find(this.filter ?? {}).create(); 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 { @HostBinding('attr.hidden') get hiddenAttribute(): string | null {
@ -124,7 +152,7 @@ export class AddonCalendarCalendarComponent implements OnInit, DoCheck, OnDestro
const source = new AddonCalendarMonthSlidesItemsManagerSource(this, moment({ const source = new AddonCalendarMonthSlidesItemsManagerSource(this, moment({
year: this.initialYear, year: this.initialYear,
month: this.initialMonth ? this.initialMonth - 1 : undefined, month: this.initialMonth ? this.initialMonth - 1 : undefined,
})); }).startOf('month'));
this.manager = new CoreSwipeSlidesDynamicItemsManager(source); this.manager = new CoreSwipeSlidesDynamicItemsManager(source);
this.managerUnsubscribe = this.manager.addListener({ this.managerUnsubscribe = this.manager.addListener({
onSelectedItemUpdated: (item) => { onSelectedItemUpdated: (item) => {
@ -176,6 +204,8 @@ export class AddonCalendarCalendarComponent implements OnInit, DoCheck, OnDestro
await this.manager?.getSource().fetchData(); await this.manager?.getSource().fetchData();
await this.manager?.getSource().load(this.manager?.getSelectedItem()); await this.manager?.getSource().load(this.manager?.getSelectedItem());
this.logView();
} catch (error) { } catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'addon.calendar.errorloadevents', true); 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 { AddonCalendarOffline } from '../../services/calendar-offline';
import { CoreCategoryData, CoreCourses } from '@features/courses/services/courses'; import { CoreCategoryData, CoreCourses } from '@features/courses/services/courses';
import { CoreConstants } from '@/core/constants'; 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. * Component that displays upcoming events.
@ -54,6 +58,7 @@ export class AddonCalendarUpcomingEventsComponent implements OnInit, DoCheck, On
protected lookAhead = 0; protected lookAhead = 0;
protected timeFormat?: string; protected timeFormat?: string;
protected differ: KeyValueDiffer<unknown, unknown>; // To detect changes in the data input. protected differ: KeyValueDiffer<unknown, unknown>; // To detect changes in the data input.
protected logView: () => void;
// Observers. // Observers.
protected undeleteEventObserver: CoreEventObserver; protected undeleteEventObserver: CoreEventObserver;
@ -84,6 +89,23 @@ export class AddonCalendarUpcomingEventsComponent implements OnInit, DoCheck, On
); );
this.differ = differs.find([]).create(); 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 { try {
await Promise.all(promises); await Promise.all(promises);
this.fetchEvents(); await this.fetchEvents();
this.logView();
} catch (error) { } catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'addon.calendar.errorloadevents', true); CoreDomUtils.showErrorModalDefault(error, 'addon.calendar.errorloadevents', true);
} }

View File

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

View File

@ -33,7 +33,7 @@ import { CoreCategoryData, CoreCourses, CoreEnrolledCourseData } from '@features
import { CoreCoursesHelper } from '@features/courses/services/courses-helper'; import { CoreCoursesHelper } from '@features/courses/services/courses-helper';
import { AddonCalendarFilterComponent } from '../../components/filter/filter'; import { AddonCalendarFilterComponent } from '../../components/filter/filter';
import moment from 'moment-timezone'; import moment from 'moment-timezone';
import { NgZone } from '@singletons'; import { NgZone, Translate } from '@singletons';
import { CoreNavigator } from '@services/navigator'; import { CoreNavigator } from '@services/navigator';
import { Params } from '@angular/router'; import { Params } from '@angular/router';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
@ -47,6 +47,9 @@ import {
} from '@classes/items-management/swipe-slides-dynamic-items-manager-source'; } from '@classes/items-management/swipe-slides-dynamic-items-manager-source';
import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker'; import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker';
import { AddonCalendarEventsSource } from '@addons/calendar/classes/events-source'; 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. * Page that displays the calendar events for a certain day.
@ -73,6 +76,7 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy {
protected onlineObserver: Subscription; protected onlineObserver: Subscription;
protected filterChangedObserver: CoreEventObserver; protected filterChangedObserver: CoreEventObserver;
protected managerUnsubscribe?: () => void; protected managerUnsubscribe?: () => void;
protected logView: () => void;
periodName?: string; periodName?: string;
manager?: CoreSwipeSlidesDynamicItemsManager<PreloadedDay, AddonCalendarDaySlidesItemsManagerSource>; manager?: CoreSwipeSlidesDynamicItemsManager<PreloadedDay, AddonCalendarDaySlidesItemsManagerSource>;
@ -186,6 +190,28 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy {
this.isOnline = CoreNetwork.isOnline(); 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'), year: CoreNavigator.getRouteNumberParam('year'),
month: month ? month - 1 : undefined, month: month ? month - 1 : undefined,
date: CoreNavigator.getRouteNumberParam('day'), date: CoreNavigator.getRouteNumberParam('day'),
})); }).startOf('day'));
this.manager = new CoreSwipeSlidesDynamicItemsManager(source); this.manager = new CoreSwipeSlidesDynamicItemsManager(source);
this.managerUnsubscribe = this.manager.addListener({ this.managerUnsubscribe = this.manager.addListener({
onSelectedItemUpdated: (item) => { onSelectedItemUpdated: (item) => {
@ -246,6 +272,8 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy {
await this.manager?.getSource().fetchData(this.filter.courseId); await this.manager?.getSource().fetchData(this.filter.courseId);
await this.manager?.getSource().load(this.manager?.getSelectedItem()); await this.manager?.getSource().load(this.manager?.getSelectedItem());
this.logView();
} catch (error) { } catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'addon.calendar.errorloadevents', true); CoreDomUtils.showErrorModalDefault(error, 'addon.calendar.errorloadevents', true);
} }
@ -500,6 +528,7 @@ class AddonCalendarDaySlidesItemsManagerSource extends CoreSwipeSlidesDynamicIte
canCreate = false; canCreate = false;
protected dayPage: AddonCalendarDayPage; protected dayPage: AddonCalendarDayPage;
protected sendLog = true;
constructor(page: AddonCalendarDayPage, initialMoment: moment.Moment) { constructor(page: AddonCalendarDayPage, initialMoment: moment.Moment) {
super({ moment: initialMoment }); super({ moment: initialMoment });
@ -780,6 +809,7 @@ class AddonCalendarDaySlidesItemsManagerSource extends CoreSwipeSlidesDynamicIte
promises.push(AddonCalendar.invalidateTimeFormat()); promises.push(AddonCalendar.invalidateTimeFormat());
this.categories = undefined; // Get categories again. this.categories = undefined; // Get categories again.
this.sendLog = true;
if (selectedDay) { if (selectedDay) {
selectedDay.dirty = true; 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 { AddonCompetencyCourseCompetenciesSource } from '@addons/competency/classes/competency-course-competencies-source';
import { CoreListItemsManager } from '@classes/items-management/list-items-manager'; import { CoreListItemsManager } from '@classes/items-management/list-items-manager';
import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker'; 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. * Page that displays the list of competencies of a learning plan.
@ -46,8 +49,11 @@ export class AddonCompetencyCompetenciesPage implements AfterViewInit, OnDestroy
title = ''; title = '';
protected logView: () => void;
constructor() { constructor() {
const planId = CoreNavigator.getRouteNumberParam('planId'); const planId = CoreNavigator.getRouteNumberParam('planId');
this.logView = CoreTime.once(() => this.performLogView());
if (!planId) { if (!planId) {
const courseId = CoreNavigator.getRequiredRouteNumberParam('courseId'); const courseId = CoreNavigator.getRequiredRouteNumberParam('courseId');
@ -96,6 +102,8 @@ export class AddonCompetencyCompetenciesPage implements AfterViewInit, OnDestroy
} else { } else {
this.title = Translate.instant('addon.competency.coursecompetencies'); this.title = Translate.instant('addon.competency.coursecompetencies');
} }
this.logView();
} catch (error) { } catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'Error getting competencies data.'); CoreDomUtils.showErrorModalDefault(error, 'Error getting competencies data.');
} }
@ -122,4 +130,42 @@ export class AddonCompetencyCompetenciesPage implements AfterViewInit, OnDestroy
this.competencies.destroy(); 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, AddonCompetency,
AddonCompetencyDataForPlanPageCompetency, AddonCompetencyDataForPlanPageCompetency,
AddonCompetencyDataForCourseCompetenciesPageCompetency, AddonCompetencyDataForCourseCompetenciesPageCompetency,
AddonCompetencyProvider,
} from '@addons/competency/services/competency'; } from '@addons/competency/services/competency';
import { CoreNavigator } from '@services/navigator'; import { CoreNavigator } from '@services/navigator';
import { IonRefresher } from '@ionic/angular'; 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 { AddonCompetencyPlanCompetenciesSource } from '@addons/competency/classes/competency-plan-competencies-source';
import { ActivatedRouteSnapshot } from '@angular/router'; import { ActivatedRouteSnapshot } from '@angular/router';
import { AddonCompetencyCourseCompetenciesSource } from '@addons/competency/classes/competency-course-competencies-source'; 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. * Page that displays the competency information.
@ -58,9 +62,11 @@ export class AddonCompetencyCompetencyPage implements OnInit, OnDestroy {
contextLevel?: string; contextLevel?: string;
contextInstanceId?: number; contextInstanceId?: number;
protected fetchSuccess = false; protected logView: () => void;
constructor() { constructor() {
this.logView = CoreTime.once(() => this.performLogView());
try { try {
const planId = CoreNavigator.getRouteNumberParam('planId'); const planId = CoreNavigator.getRouteNumberParam('planId');
@ -156,31 +162,7 @@ export class AddonCompetencyCompetencyPage implements OnInit, OnDestroy {
} }
}); });
if (!this.fetchSuccess) { this.logView();
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,
),
);
}
}
} catch (error) { } catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'Error getting competency data.'); CoreDomUtils.showErrorModalDefault(error, 'Error getting competency data.');
} }
@ -288,6 +270,73 @@ export class AddonCompetencyCompetencyPage implements OnInit, OnDestroy {
return competency.usercompetencysummary; 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 { CoreDomUtils } from '@services/utils/dom';
import { CoreUtils } from '@services/utils/utils'; import { CoreUtils } from '@services/utils/utils';
import { ADDON_COMPETENCY_SUMMARY_PAGE } from '@addons/competency/competency.module'; 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. * Page that displays the competency summary.
@ -36,7 +38,30 @@ export class AddonCompetencyCompetencySummaryPage implements OnInit {
contextLevel?: ContextLevel; contextLevel?: ContextLevel;
contextInstanceId?: number; 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 * @inheritdoc
@ -77,10 +102,7 @@ export class AddonCompetencyCompetencySummaryPage implements OnInit {
this.competency = result.competency; this.competency = result.competency;
if (!this.fetchSuccess) { this.logView();
this.fetchSuccess = true;
CoreUtils.ignoreErrors(AddonCompetency.logCompetencyView(this.competencyId, this.competency.competency.shortname));
}
} catch (error) { } catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'Error getting competency summary data.'); 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 { CoreListItemsManager } from '@classes/items-management/list-items-manager';
import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker'; import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker';
import { AddonCompetencyCourseCompetenciesSource } from '@addons/competency/classes/competency-course-competencies-source'; 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. * Page that displays the list of competencies of a course.
@ -41,7 +45,11 @@ export class AddonCompetencyCourseCompetenciesPage implements OnInit, OnDestroy
AddonCompetencyCourseCompetenciesSource AddonCompetencyCourseCompetenciesSource
>; >;
protected logView: () => void;
constructor() { constructor() {
this.logView = CoreTime.once(() => this.performLogView());
try { try {
const courseId = CoreNavigator.getRequiredRouteNumberParam('courseId'); const courseId = CoreNavigator.getRequiredRouteNumberParam('courseId');
const userId = CoreNavigator.getRouteNumberParam('userId'); const userId = CoreNavigator.getRouteNumberParam('userId');
@ -53,7 +61,6 @@ export class AddonCompetencyCourseCompetenciesPage implements OnInit, OnDestroy
this.competencies = new CoreListItemsManager(source, AddonCompetencyCourseCompetenciesPage); this.competencies = new CoreListItemsManager(source, AddonCompetencyCourseCompetenciesPage);
} catch (error) { } catch (error) {
CoreDomUtils.showErrorModal(error); CoreDomUtils.showErrorModal(error);
CoreNavigator.back(); CoreNavigator.back();
return; return;
@ -112,6 +119,8 @@ export class AddonCompetencyCourseCompetenciesPage implements OnInit, OnDestroy
protected async fetchCourseCompetencies(): Promise<void> { protected async fetchCourseCompetencies(): Promise<void> {
try { try {
await this.competencies.getSource().reload(); await this.competencies.getSource().reload();
this.logView();
} catch (error) { } catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'Error getting course competencies data.'); 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 { AddonCompetencyPlansSource } from '@addons/competency/classes/competency-plans-source';
import { CoreListItemsManager } from '@classes/items-management/list-items-manager'; import { CoreListItemsManager } from '@classes/items-management/list-items-manager';
import { AddonCompetencyPlanCompetenciesSource } from '@addons/competency/classes/competency-plan-competencies-source'; 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. * Page that displays a learning plan.
@ -36,7 +38,11 @@ export class AddonCompetencyPlanPage implements OnInit, OnDestroy {
plans!: CoreSwipeNavigationItemsManager; plans!: CoreSwipeNavigationItemsManager;
competencies!: CoreListItemsManager<AddonCompetencyDataForPlanPageCompetency, AddonCompetencyPlanCompetenciesSource>; competencies!: CoreListItemsManager<AddonCompetencyDataForPlanPageCompetency, AddonCompetencyPlanCompetenciesSource>;
protected logView: () => void;
constructor() { constructor() {
this.logView = CoreTime.once(() => this.performLogView());
try { try {
const planId = CoreNavigator.getRequiredRouteNumberParam('planId'); const planId = CoreNavigator.getRequiredRouteNumberParam('planId');
const userId = CoreNavigator.getRouteNumberParam('userId'); const userId = CoreNavigator.getRouteNumberParam('userId');
@ -93,6 +99,8 @@ export class AddonCompetencyPlanPage implements OnInit, OnDestroy {
protected async fetchLearningPlan(): Promise<void> { protected async fetchLearningPlan(): Promise<void> {
try { try {
await this.competencies.getSource().reload(); await this.competencies.getSource().reload();
this.logView();
} catch (error) { } catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'Error getting learning plan data.'); 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 { AddonCompetencyPlanFormatted, AddonCompetencyPlansSource } from '@addons/competency/classes/competency-plans-source';
import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker'; import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker';
import { CoreListItemsManager } from '@classes/items-management/list-items-manager'; 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. * Page that displays the list of learning plans.
@ -34,11 +38,25 @@ export class AddonCompetencyPlanListPage implements AfterViewInit, OnDestroy {
plans: CoreListItemsManager<AddonCompetencyPlanFormatted, AddonCompetencyPlansSource>; plans: CoreListItemsManager<AddonCompetencyPlanFormatted, AddonCompetencyPlansSource>;
protected logView: () => void;
constructor() { constructor() {
const userId = CoreNavigator.getRouteNumberParam('userId'); const userId = CoreNavigator.getRouteNumberParam('userId');
const source = CoreRoutedItemsManagerSourcesTracker.getOrCreateSource(AddonCompetencyPlansSource, [userId]); const source = CoreRoutedItemsManagerSourcesTracker.getOrCreateSource(AddonCompetencyPlansSource, [userId]);
this.plans = new CoreListItemsManager(source, AddonCompetencyPlanListPage); 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> { protected async fetchLearningPlans(): Promise<void> {
try { try {
await this.plans.load(); await this.plans.load();
this.logView();
} catch (error) { } catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'Error getting learning plans data.'); 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 { CoreSite, CoreSiteWSPreSets } from '@classes/site';
import { CoreCommentsArea } from '@features/comments/services/comments'; import { CoreCommentsArea } from '@features/comments/services/comments';
import { CoreCourseSummary, CoreCourseModuleSummary } from '@features/course/services/course'; import { CoreCourseSummary, CoreCourseModuleSummary } from '@features/course/services/course';
import { CorePushNotifications } from '@features/pushnotifications/services/pushnotifications';
import { CoreUserSummary } from '@features/user/services/user'; import { CoreUserSummary } from '@features/user/services/user';
import { CoreSites } from '@services/sites'; import { CoreSites } from '@services/sites';
import { CoreUtils } from '@services/utils/utils'; import { CoreUtils } from '@services/utils/utils';
@ -495,7 +494,7 @@ export class AddonCompetencyProvider {
* @param planId ID of the plan. * @param planId ID of the plan.
* @param competencyId ID of the competency. * @param competencyId ID of the competency.
* @param planStatus Current plan Status to decide what action should be logged. * @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 userId User ID. If not defined, current user.
* @param siteId Site ID. If not defined, current site. * @param siteId Site ID. If not defined, current site.
* @returns Promise resolved when the WS call is successful. * @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_plan_viewed'
: 'core_competency_user_competency_viewed_in_plan'; : '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); await site.write(wsName, params, preSets);
} }
@ -539,7 +532,7 @@ export class AddonCompetencyProvider {
* *
* @param courseId ID of the course. * @param courseId ID of the course.
* @param competencyId ID of the competency. * @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 userId User ID. If not defined, current user.
* @param siteId Site ID. If not defined, current site. * @param siteId Site ID. If not defined, current site.
* @returns Promise resolved when the WS call is successful. * @returns Promise resolved when the WS call is successful.
@ -564,14 +557,7 @@ export class AddonCompetencyProvider {
typeExpected: 'boolean', typeExpected: 'boolean',
}; };
const wsName = 'core_competency_user_competency_viewed_in_course'; await site.write('core_competency_user_competency_viewed_in_course', params, preSets);
CorePushNotifications.logViewEvent(competencyId, name, 'competency', 'wsName', {
courseid: courseId,
userid: userId,
}, siteId);
await site.write(wsName, params, preSets);
} }
/** /**
@ -593,10 +579,7 @@ export class AddonCompetencyProvider {
typeExpected: 'boolean', typeExpected: 'boolean',
}; };
const wsName = 'core_competency_competency_viewed'; await site.write('core_competency_competency_viewed', params, preSets);
CorePushNotifications.logViewEvent(competencyId, name, 'competency', wsName, {}, siteId);
await site.write(wsName, params, preSets);
} }
} }

View File

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

View File

@ -57,7 +57,7 @@ export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityCo
@ViewChild(AddonModAssignSubmissionComponent) submissionComponent?: AddonModAssignSubmissionComponent; @ViewChild(AddonModAssignSubmissionComponent) submissionComponent?: AddonModAssignSubmissionComponent;
component = AddonModAssignProvider.COMPONENT; component = AddonModAssignProvider.COMPONENT;
moduleName = 'assign'; pluginName = 'assign';
assign?: AddonModAssignAssign; // The assign object. assign?: AddonModAssignAssign; // The assign object.
canViewAllSubmissions = false; // Whether the user can view all submissions. canViewAllSubmissions = false; // Whether the user can view all submissions.
@ -230,14 +230,20 @@ export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityCo
return; // Shouldn't happen. 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) { if (this.canViewAllSubmissions) {
// User can see all submissions, log grading view. // 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) { } else if (this.canViewOwnSubmission) {
// User can only see their own submission, log view the user submission. // 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.", "gradelocked": "This grade is locked or overridden in the gradebook.",
"gradenotsynced": "Grade not synced", "gradenotsynced": "Grade not synced",
"gradeoutof": "Grade out of {{$a}}", "gradeoutof": "Grade out of {{$a}}",
"grading": "Grading",
"gradingstatus": "Grading status", "gradingstatus": "Grading status",
"groupsubmissionsettings": "Group submission settings", "groupsubmissionsettings": "Group submission settings",
"hiddenuser": "Participant", "hiddenuser": "Participant",
@ -101,6 +102,7 @@
"submittedlate": "Assignment was submitted {{$a}} late", "submittedlate": "Assignment was submitted {{$a}} late",
"submittedovertime": "Assignment was submitted {{$a}} over the time limit", "submittedovertime": "Assignment was submitted {{$a}} over the time limit",
"submittedundertime": "Assignment was submitted {{$a}} under the time limit", "submittedundertime": "Assignment was submitted {{$a}} under the time limit",
"subpagetitle": "{{$a.contextname}} - {{$a.subpage}}",
"syncblockedusercomponent": "user grade", "syncblockedusercomponent": "user grade",
"timelimit": "Time limit", "timelimit": "Time limit",
"timemodified": "Last modified", "timemodified": "Last modified",

View File

@ -39,6 +39,7 @@ import { AddonModAssignOffline } from '../../services/assign-offline';
import { AddonModAssignSync } from '../../services/assign-sync'; import { AddonModAssignSync } from '../../services/assign-sync';
import { CoreUtils } from '@services/utils/utils'; import { CoreUtils } from '@services/utils/utils';
import { CoreWSExternalFile } from '@services/ws'; import { CoreWSExternalFile } from '@services/ws';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
/** /**
* Page that allows adding or editing an assigment submission. * Page that allows adding or editing an assigment submission.
@ -226,6 +227,17 @@ export class AddonModAssignEditPage implements OnInit, OnDestroy, CanLeave {
// No offline data found. // No offline data found.
this.hasOffline = false; 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) { } catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'Error getting assigment data.'); CoreDomUtils.showErrorModalDefault(error, 'Error getting assigment data.');

View File

@ -34,6 +34,7 @@ import {
AddonModAssignManualSyncData, AddonModAssignManualSyncData,
AddonModAssignAutoSyncData, AddonModAssignAutoSyncData,
} from '../../services/assign-sync'; } from '../../services/assign-sync';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
/** /**
* Page that displays a list of submissions of an assignment. * 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> { protected async fetchAssignment(sync = false): Promise<void> {
try { try {
await this.submissions.getSource().loadAssignment(sync); 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) { } catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'Error getting assigment data.'); 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 { AddonModAssignListFilterName, AddonModAssignSubmissionsSource } from '../../classes/submissions-source';
import { AddonModAssignSubmissionComponent } from '../../components/submission/submission'; import { AddonModAssignSubmissionComponent } from '../../components/submission/submission';
import { AddonModAssign, AddonModAssignAssign } from '../../services/assign'; 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. * 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 assign?: AddonModAssignAssign; // The assignment the submission belongs to.
protected blindMarking = false; // Whether it uses blind marking. protected blindMarking = false; // Whether it uses blind marking.
protected forceLeave = false; // To allow leaving the page without checking for changes. 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 * @inheritdoc
@ -84,6 +108,7 @@ export class AddonModAssignSubmissionReviewPage implements OnInit, OnDestroy, Ca
} }
this.fetchSubmission().finally(() => { this.fetchSubmission().finally(() => {
this.logView();
this.loaded = true; this.loaded = true;
}); });
}); });

View File

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

View File

@ -44,7 +44,7 @@ import {
export class AddonModBBBIndexComponent extends CoreCourseModuleMainActivityComponent implements OnInit { export class AddonModBBBIndexComponent extends CoreCourseModuleMainActivityComponent implements OnInit {
component = AddonModBBBService.COMPONENT; component = AddonModBBBService.COMPONENT;
moduleName = 'bigbluebuttonbn'; pluginName = 'bigbluebuttonbn';
bbb?: AddonModBBBData; bbb?: AddonModBBBData;
groupInfo?: CoreGroupInfo; groupInfo?: CoreGroupInfo;
groupId = 0; groupId = 0;
@ -226,7 +226,9 @@ export class AddonModBBBIndexComponent extends CoreCourseModuleMainActivityCompo
return; // Shouldn't happen. 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. * Report a BBB as being viewed.
* *
* @param id BBB instance ID. * @param id BBB instance ID.
* @param name Name of the BBB.
* @param siteId Site ID. If not defined, current site. * @param siteId Site ID. If not defined, current site.
* @returns Promise resolved when the WS call is successful. * @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 = { const params: AddonModBBBViewBigBlueButtonBNWSParams = {
bigbluebuttonbnid: id, bigbluebuttonbnid: id,
}; };
await CoreCourseLogHelper.logSingle( await CoreCourseLogHelper.log(
'mod_bigbluebuttonbn_view_bigbluebuttonbn', 'mod_bigbluebuttonbn_view_bigbluebuttonbn',
params, params,
AddonModBBBService.COMPONENT, AddonModBBBService.COMPONENT,
id, id,
name,
'bigbluebuttonbn',
{},
siteId, siteId,
); );
} }

View File

@ -19,6 +19,7 @@ import { CoreCourseContentsPage } from '@features/course/pages/contents/contents
import { CoreCourse } from '@features/course/services/course'; import { CoreCourse } from '@features/course/services/course';
import { CoreNavigator } from '@services/navigator'; import { CoreNavigator } from '@services/navigator';
import { AddonModBookModuleHandlerService } from '../../services/handlers/module'; import { AddonModBookModuleHandlerService } from '../../services/handlers/module';
import { CoreUtils } from '@services/utils/utils';
/** /**
* Component that displays a book entry page. * 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 { export class AddonModBookIndexComponent extends CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy {
pluginName = 'book';
showNumbers = true; showNumbers = true;
addPadding = true; addPadding = true;
showBullets = false; showBullets = false;
@ -102,7 +104,9 @@ export class AddonModBookIndexComponent extends CoreCourseModuleMainResourceComp
* @inheritdoc * @inheritdoc
*/ */
protected async logActivity(): Promise<void> { 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, AddonModBookProvider,
AddonModBookTocChapter, AddonModBookTocChapter,
} from '../../services/book'; } from '../../services/book';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
import { CoreUrlUtils } from '@services/utils/url';
/** /**
* Page that displays a book contents. * Page that displays a book contents.
@ -286,7 +288,15 @@ export class AddonModBookContentsPage implements OnInit, OnDestroy {
} }
// Chapter loaded, log view. // 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 currentChapterIndex = this.chapters.findIndex((chapter) => chapter.id == chapterId);
const isLastChapter = currentChapterIndex < 0 || this.chapters[currentChapterIndex + 1] === undefined; const isLastChapter = currentChapterIndex < 0 || this.chapters[currentChapterIndex + 1] === undefined;

View File

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

View File

@ -32,7 +32,7 @@ import { AddonModChatModuleHandlerService } from '../../services/handlers/module
export class AddonModChatIndexComponent extends CoreCourseModuleMainActivityComponent implements OnInit { export class AddonModChatIndexComponent extends CoreCourseModuleMainActivityComponent implements OnInit {
component = AddonModChatProvider.COMPONENT; component = AddonModChatProvider.COMPONENT;
moduleName = 'chat'; pluginName = 'chat';
chat?: AddonModChatChat; chat?: AddonModChatChat;
chatInfo?: { chatInfo?: {
date: string; date: string;
@ -85,7 +85,9 @@ export class AddonModChatIndexComponent extends CoreCourseModuleMainActivityComp
return; // Shouldn't happen. 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 { AddonModChatUsersModalComponent, AddonModChatUsersModalResult } from '../../components/users-modal/users-modal';
import { AddonModChat, AddonModChatProvider, AddonModChatUser } from '../../services/chat'; import { AddonModChat, AddonModChatProvider, AddonModChatUser } from '../../services/chat';
import { AddonModChatFormattedMessage, AddonModChatHelper } from '../../services/chat-helper'; import { AddonModChatFormattedMessage, AddonModChatHelper } from '../../services/chat-helper';
import { CoreTime } from '@singletons/time';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
/** /**
* Page that displays a chat session. * Page that displays a chat session.
@ -61,6 +63,7 @@ export class AddonModChatChatPage implements OnInit, OnDestroy, CanLeave {
protected viewDestroyed = false; protected viewDestroyed = false;
protected pollingRunning = false; protected pollingRunning = false;
protected users: AddonModChatUser[] = []; protected users: AddonModChatUser[] = [];
protected logView: () => void;
constructor() { constructor() {
this.currentUserId = CoreSites.getCurrentSiteUserId(); this.currentUserId = CoreSites.getCurrentSiteUserId();
@ -71,6 +74,16 @@ export class AddonModChatChatPage implements OnInit, OnDestroy, CanLeave {
this.isOnline = CoreNetwork.isOnline(); 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(); await this.fetchMessages();
this.startPolling(); this.startPolling();
this.logView();
} catch (error) { } catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'addon.mod_chat.errorwhileconnecting', true); CoreDomUtils.showErrorModalDefault(error, 'addon.mod_chat.errorwhileconnecting', true);
CoreNavigator.back(); CoreNavigator.back();

View File

@ -21,6 +21,9 @@ import { CoreDomUtils } from '@services/utils/dom';
import { CoreUtils } from '@services/utils/utils'; import { CoreUtils } from '@services/utils/utils';
import { AddonModChat } from '../../services/chat'; import { AddonModChat } from '../../services/chat';
import { AddonModChatFormattedSessionMessage, AddonModChatHelper } from '../../services/chat-helper'; 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. * Page that displays list of chat session messages.
@ -42,6 +45,19 @@ export class AddonModChatSessionMessagesPage implements OnInit {
protected sessionStart!: number; protected sessionStart!: number;
protected sessionEnd!: number; protected sessionEnd!: number;
protected groupId!: 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 * @inheritdoc

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // 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 { CoreListItemsManager } from '@classes/items-management/list-items-manager';
import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker'; import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker';
import { CoreSplitViewComponent } from '@components/split-view/split-view'; import { CoreSplitViewComponent } from '@components/split-view/split-view';
@ -21,6 +21,9 @@ import { CoreGroupInfo } from '@services/groups';
import { CoreNavigator } from '@services/navigator'; import { CoreNavigator } from '@services/navigator';
import { CoreDomUtils } from '@services/utils/dom'; import { CoreDomUtils } from '@services/utils/dom';
import { AddonModChatSessionFormatted, AddonModChatSessionsSource } from '../../classes/chat-sessions-source'; 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. * Page that displays list of chat sessions.
@ -29,14 +32,32 @@ import { AddonModChatSessionFormatted, AddonModChatSessionsSource } from '../../
selector: 'page-addon-mod-chat-sessions', selector: 'page-addon-mod-chat-sessions',
templateUrl: 'sessions.html', templateUrl: 'sessions.html',
}) })
export class AddonModChatSessionsPage implements AfterViewInit, OnDestroy { export class AddonModChatSessionsPage implements OnInit, AfterViewInit, OnDestroy {
@ViewChild(CoreSplitViewComponent) splitView!: CoreSplitViewComponent; @ViewChild(CoreSplitViewComponent) splitView!: CoreSplitViewComponent;
sessions!: CoreListItemsManager<AddonModChatSessionFormatted, AddonModChatSessionsSource>; sessions!: CoreListItemsManager<AddonModChatSessionFormatted, AddonModChatSessionsSource>;
courseId?: number; courseId?: number;
protected logView: () => void;
constructor() { 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 { try {
this.courseId = CoreNavigator.getRequiredRouteNumberParam('courseId'); this.courseId = CoreNavigator.getRequiredRouteNumberParam('courseId');
const chatId = CoreNavigator.getRequiredRouteNumberParam('chatId'); const chatId = CoreNavigator.getRequiredRouteNumberParam('chatId');
@ -91,6 +112,8 @@ export class AddonModChatSessionsPage implements AfterViewInit, OnDestroy {
async fetchSessions(): Promise<void> { async fetchSessions(): Promise<void> {
try { try {
await this.sessions.load(); await this.sessions.load();
this.logView();
} catch (error) { } catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'core.errorloadingcontent', true); CoreDomUtils.showErrorModalDefault(error, 'core.errorloadingcontent', true);
} }

View File

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

View File

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

View File

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

View File

@ -59,7 +59,7 @@ const contentToken = '<!-- CORE-DATABASE-CONTENT-GOES-HERE -->';
export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComponent implements OnInit, OnDestroy { export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComponent implements OnInit, OnDestroy {
component = AddonModDataProvider.COMPONENT; component = AddonModDataProvider.COMPONENT;
moduleName = 'data'; pluginName = 'data';
access?: AddonModDataGetDataAccessInformationWSResponse; access?: AddonModDataGetDataAccessInformationWSResponse;
database?: AddonModDataData; database?: AddonModDataData;
@ -420,8 +420,6 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp
try { try {
await this.fetchEntriesData(); await this.fetchEntriesData();
// Log activity view for coherence with Moodle web.
await this.logActivity();
} catch (error) { } catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'core.course.errorgetmodule', true); CoreDomUtils.showErrorModalDefault(error, 'core.course.errorgetmodule', true);
} finally { } finally {
@ -470,9 +468,6 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp
try { try {
await this.fetchEntriesData(); await this.fetchEntriesData();
// Log activity view for coherence with Moodle web.
return this.logActivity();
} catch (error) { } catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'core.course.errorgetmodule', true); CoreDomUtils.showErrorModalDefault(error, 'core.course.errorgetmodule', true);
} }
@ -535,7 +530,9 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp
return; 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 { CoreDom } from '@singletons/dom';
import { AddonModDataEntryFieldInitialized } from '../../classes/base-field-plugin-component'; import { AddonModDataEntryFieldInitialized } from '../../classes/base-field-plugin-component';
import { CoreTextUtils } from '@services/utils/text'; import { CoreTextUtils } from '@services/utils/text';
import { CoreTime } from '@singletons/time';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
/** /**
* Page that displays the view edit page. * Page that displays the view edit page.
@ -65,6 +67,7 @@ export class AddonModDataEditPage implements OnInit {
protected initialSelectedGroup?: number; protected initialSelectedGroup?: number;
protected isEditing = false; protected isEditing = false;
protected originalData: AddonModDataEntryFields = {}; protected originalData: AddonModDataEntryFields = {};
protected logView: () => void;
entry?: AddonModDataEntry; entry?: AddonModDataEntry;
fields: Record<number, AddonModDataField> = {}; fields: Record<number, AddonModDataField> = {};
@ -94,6 +97,20 @@ export class AddonModDataEditPage implements OnInit {
constructor() { constructor() {
this.siteId = CoreSites.getCurrentSiteId(); this.siteId = CoreSites.getCurrentSiteId();
this.editForm = new FormGroup({}); 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.editFormRender = this.displayEditFields();
this.logView();
} catch (error) { } catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'core.course.errorgetmodule', true); CoreDomUtils.showErrorModalDefault(error, 'core.course.errorgetmodule', true);
} }

View File

@ -36,6 +36,8 @@ import { AddonModDataProvider,
} from '../../services/data'; } from '../../services/data';
import { AddonModDataHelper } from '../../services/data-helper'; import { AddonModDataHelper } from '../../services/data-helper';
import { AddonModDataSyncProvider } from '../../services/data-sync'; import { AddonModDataSyncProvider } from '../../services/data-sync';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
import { CoreTime } from '@singletons/time';
/** /**
* Page that displays the view entry page. * 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 entryChangedObserver: CoreEventObserver; // It will observe the changed entry event.
protected fields: Record<number, AddonModDataField> = {}; protected fields: Record<number, AddonModDataField> = {};
protected fieldsArray: AddonModDataField[] = []; protected fieldsArray: AddonModDataField[] = [];
protected logAfterFetch = true;
protected sortBy = 0; protected sortBy = 0;
protected sortDirection = 'DESC'; protected sortDirection = 'DESC';
protected logView: () => void;
moduleId = 0; moduleId = 0;
courseId!: number; courseId!: number;
@ -129,6 +131,8 @@ export class AddonModDataEntryPage implements OnInit, OnDestroy {
} }
} }
}, this.siteId); }, this.siteId);
this.logView = CoreTime.once(() => this.performLogView());
} }
/** /**
@ -219,13 +223,7 @@ export class AddonModDataEntryPage implements OnInit, OnDestroy {
access: this.access, access: this.access,
}; };
if (this.logAfterFetch) { this.logView();
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);
}
} catch (error) { } catch (error) {
if (!refresh) { if (!refresh) {
// Some call failed, retry without using cache since it might be a new activity. // 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.entryId = undefined;
this.entry = undefined; this.entry = undefined;
this.entryLoaded = false; this.entryLoaded = false;
this.logAfterFetch = true; this.logView = CoreTime.once(() => this.performLogView()); // Log again after loading data.
await this.fetchEntryData(); await this.fetchEntryData();
} }
@ -310,7 +308,6 @@ export class AddonModDataEntryPage implements OnInit, OnDestroy {
this.entry = undefined; this.entry = undefined;
this.entryId = undefined; this.entryId = undefined;
this.entryLoaded = false; this.entryLoaded = false;
this.logAfterFetch = true;
await this.fetchEntryData(); await this.fetchEntryData();
} }
@ -422,6 +419,28 @@ export class AddonModDataEntryPage implements OnInit, OnDestroy {
AddonModData.invalidateEntryData(this.database!.id, this.entryId!); 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 * @inheritdoc
*/ */

View File

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

View File

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

View File

@ -56,7 +56,7 @@ export class AddonModFeedbackIndexComponent extends CoreCourseModuleMainActivity
@Input() group = 0; @Input() group = 0;
component = AddonModFeedbackProvider.COMPONENT; component = AddonModFeedbackProvider.COMPONENT;
moduleName = 'feedback'; pluginName = 'feedback';
feedback?: AddonModFeedbackWSFeedback; feedback?: AddonModFeedbackWSFeedback;
goPage?: number; goPage?: number;
items: AddonModFeedbackItem[] = []; items: AddonModFeedbackItem[] = [];
@ -140,7 +140,18 @@ export class AddonModFeedbackIndexComponent extends CoreCourseModuleMainActivity
return; // Shouldn't happen. 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. * @param tabName New tab name.
*/ */
tabChanged(tabName: string): void { tabChanged(tabName: string): void {
const tabHasChanged = this.tab !== undefined && this.tab !== tabName;
this.tab = tabName; this.tab = tabName;
if (!this.tabsLoaded[this.tab]) { if (!this.tabsLoaded[this.tab]) {
this.loadContent(false, false, true); this.loadContent(false, false, true);
} }
if (tabHasChanged) {
this.callAnalyticsLogEvent();
}
} }
/** /**

View File

@ -27,6 +27,8 @@ import {
AddonModFeedbackWSFeedback, AddonModFeedbackWSFeedback,
} from '../../services/feedback'; } from '../../services/feedback';
import { AddonModFeedbackAttempt, AddonModFeedbackFormItem, AddonModFeedbackHelper } from '../../services/feedback-helper'; 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. * Page that displays a feedback attempt review.
@ -48,6 +50,7 @@ export class AddonModFeedbackAttemptPage implements OnInit, OnDestroy {
loaded = false; loaded = false;
protected attemptId: number; protected attemptId: number;
protected logView: () => void;
constructor() { constructor() {
this.cmId = CoreNavigator.getRequiredRouteNumberParam('cmId'); this.cmId = CoreNavigator.getRequiredRouteNumberParam('cmId');
@ -60,6 +63,21 @@ export class AddonModFeedbackAttemptPage implements OnInit, OnDestroy {
); );
this.attempts = new AddonModFeedbackAttemptsSwipeManager(source); 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; return attemptItem;
}).filter((itemData) => itemData); // Filter items with errors. }).filter((itemData) => itemData); // Filter items with errors.
this.logView();
} catch (message) { } catch (message) {
// Some call failed on fetch, go back. // Some call failed on fetch, go back.
CoreDomUtils.showErrorModalDefault(message, 'core.course.errorgetmodule', true); 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 { CoreUtils } from '@services/utils/utils';
import { AddonModFeedbackAttemptItem, AddonModFeedbackAttemptsSource } from '../../classes/feedback-attempts-source'; import { AddonModFeedbackAttemptItem, AddonModFeedbackAttemptsSource } from '../../classes/feedback-attempts-source';
import { AddonModFeedbackWSAnonAttempt, AddonModFeedbackWSAttempt } from '../../services/feedback'; import { AddonModFeedbackWSAnonAttempt, AddonModFeedbackWSAttempt } from '../../services/feedback';
import { CoreTime } from '@singletons/time';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
/** /**
* Page that displays feedback attempts. * Page that displays feedback attempts.
@ -41,8 +43,25 @@ export class AddonModFeedbackAttemptsPage implements AfterViewInit, OnDestroy {
fetchFailed = false; fetchFailed = false;
courseId?: number; courseId?: number;
protected logView: () => void;
constructor(protected route: ActivatedRoute) { constructor(protected route: ActivatedRoute) {
this.promisedAttempts = new CorePromisedValue(); 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 { get attempts(): AddonModFeedbackAttemptsManager | null {
@ -112,6 +131,8 @@ export class AddonModFeedbackAttemptsPage implements AfterViewInit, OnDestroy {
await attempts.getSource().loadFeedback(); await attempts.getSource().loadFeedback();
await attempts.load(); await attempts.load();
this.logView();
} catch (error) { } catch (error) {
this.fetchFailed = true; this.fetchFailed = true;

View File

@ -38,6 +38,7 @@ import {
import { AddonModFeedbackFormItem, AddonModFeedbackHelper } from '../../services/feedback-helper'; import { AddonModFeedbackFormItem, AddonModFeedbackHelper } from '../../services/feedback-helper';
import { AddonModFeedbackSync } from '../../services/feedback-sync'; import { AddonModFeedbackSync } from '../../services/feedback-sync';
import { AddonModFeedbackModuleHandlerService } from '../../services/handlers/module'; import { AddonModFeedbackModuleHandlerService } from '../../services/handlers/module';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
/** /**
* Page that displays feedback form. * Page that displays feedback form.
@ -122,7 +123,7 @@ export class AddonModFeedbackFormPage implements OnInit, OnDestroy, CanLeave {
} }
try { 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); CoreCourse.checkModuleCompletion(this.courseId, this.module!.completiondata);
} catch { } catch {
@ -263,6 +264,8 @@ export class AddonModFeedbackFormPage implements OnInit, OnDestroy, CanLeave {
const itemsCopy = CoreUtils.clone(this.items); // Copy the array to avoid modifications. const itemsCopy = CoreUtils.clone(this.items); // Copy the array to avoid modifications.
this.originalData = AddonModFeedbackHelper.getPageItemsResponses(itemsCopy); 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 * @inheritdoc
*/ */

View File

@ -20,6 +20,8 @@ import { CoreDomUtils } from '@services/utils/dom';
import { CoreUtils } from '@services/utils/utils'; import { CoreUtils } from '@services/utils/utils';
import { AddonModFeedback, AddonModFeedbackWSFeedback } from '../../services/feedback'; import { AddonModFeedback, AddonModFeedbackWSFeedback } from '../../services/feedback';
import { AddonModFeedbackHelper, AddonModFeedbackNonRespondent } from '../../services/feedback-helper'; import { AddonModFeedbackHelper, AddonModFeedbackNonRespondent } from '../../services/feedback-helper';
import { CoreTime } from '@singletons/time';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
/** /**
* Page that displays feedback non respondents. * Page that displays feedback non respondents.
@ -33,6 +35,7 @@ export class AddonModFeedbackNonRespondentsPage implements OnInit {
protected cmId!: number; protected cmId!: number;
protected feedback?: AddonModFeedbackWSFeedback; protected feedback?: AddonModFeedbackWSFeedback;
protected page = 0; protected page = 0;
protected logView: () => void;
courseId!: number; courseId!: number;
selectedGroup!: number; selectedGroup!: number;
@ -43,6 +46,22 @@ export class AddonModFeedbackNonRespondentsPage implements OnInit {
loaded = false; loaded = false;
loadMoreError = 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 * @inheritdoc
*/ */
@ -81,6 +100,8 @@ export class AddonModFeedbackNonRespondentsPage implements OnInit {
this.selectedGroup = CoreGroups.validateGroupId(this.selectedGroup, this.groupInfo); this.selectedGroup = CoreGroups.validateGroupId(this.selectedGroup, this.groupInfo);
await this.loadGroupUsers(this.selectedGroup); await this.loadGroupUsers(this.selectedGroup);
this.logView();
} catch (message) { } catch (message) {
CoreDomUtils.showErrorModalDefault(message, 'core.course.errorgetmodule', true); CoreDomUtils.showErrorModalDefault(message, 'core.course.errorgetmodule', true);

View File

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

View File

@ -22,6 +22,7 @@ import { Md5 } from 'ts-md5';
import { AddonModFolder, AddonModFolderFolder, AddonModFolderProvider } from '../../services/folder'; import { AddonModFolder, AddonModFolderFolder, AddonModFolderProvider } from '../../services/folder';
import { AddonModFolderFolderFormattedData, AddonModFolderHelper } from '../../services/folder-helper'; import { AddonModFolderFolderFormattedData, AddonModFolderHelper } from '../../services/folder-helper';
import { AddonModFolderModuleHandlerService } from '../../services/handlers/module'; import { AddonModFolderModuleHandlerService } from '../../services/handlers/module';
import { CoreUtils } from '@services/utils/utils';
/** /**
* Component that displays a folder. * Component that displays a folder.
@ -39,6 +40,7 @@ export class AddonModFolderIndexComponent extends CoreCourseModuleMainResourceCo
@Input() subfolder?: AddonModFolderFolderFormattedData; // Subfolder to show. @Input() subfolder?: AddonModFolderFolderFormattedData; // Subfolder to show.
component = AddonModFolderProvider.COMPONENT; component = AddonModFolderProvider.COMPONENT;
pluginName = 'folder';
contents?: AddonModFolderFolderFormattedData; contents?: AddonModFolderFolderFormattedData;
constructor(@Optional() courseContentsPage?: CoreCourseContentsPage) { constructor(@Optional() courseContentsPage?: CoreCourseContentsPage) {
@ -119,7 +121,9 @@ export class AddonModFolderIndexComponent extends CoreCourseModuleMainResourceCo
* @inheritdoc * @inheritdoc
*/ */
protected async logActivity(): Promise<void> { 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. * Report a folder as being viewed.
* *
* @param id Module ID. * @param id Module ID.
* @param name Name of the folder.
* @param siteId Site ID. If not defined, current site. * @param siteId Site ID. If not defined, current site.
* @returns Promise resolved when the WS call is successful. * @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 = { const params: AddonModFolderViewFolderWSParams = {
folderid: id, folderid: id,
}; };
await CoreCourseLogHelper.logSingle( await CoreCourseLogHelper.log(
'mod_folder_view_folder', 'mod_folder_view_folder',
params, params,
AddonModFolderProvider.COMPONENT, AddonModFolderProvider.COMPONENT,
id, id,
name,
'folder',
{},
siteId, siteId,
); );
} }

View File

@ -71,7 +71,7 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
@ViewChild(CoreSplitViewComponent) splitView!: CoreSplitViewComponent; @ViewChild(CoreSplitViewComponent) splitView!: CoreSplitViewComponent;
component = AddonModForumProvider.COMPONENT; component = AddonModForumProvider.COMPONENT;
moduleName = 'forum'; pluginName = 'forum';
descriptionNote?: string; descriptionNote?: string;
promisedDiscussions: CorePromisedValue<AddonModForumDiscussionsManager>; promisedDiscussions: CorePromisedValue<AddonModForumDiscussionsManager>;
discussionsItems: AddonModForumDiscussionItem[] = []; discussionsItems: AddonModForumDiscussionItem[] = [];
@ -708,12 +708,14 @@ class AddonModForumDiscussionsManager extends CoreListItemsManager<AddonModForum
} }
try { try {
await AddonModForum.logView(forum.id, forum.name); await AddonModForum.logView(forum.id);
CoreCourse.checkModuleCompletion(this.page.courseId, this.page.module.completiondata); CoreCourse.checkModuleCompletion(this.page.courseId, this.page.module.completiondata);
} catch { } catch {
// Ignore errors. // 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 { CoreFileEntry } from '@services/file-helper';
import { AddonModForumSharedPostFormData } from '../../pages/discussion/discussion'; import { AddonModForumSharedPostFormData } from '../../pages/discussion/discussion';
import { CoreDom } from '@singletons/dom'; 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.). * 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. * Deletes an online post.
*/ */
async deletePost(): Promise<void> { 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 { try {
await CoreDomUtils.showDeleteConfirm('addon.mod_forum.deletesure'); await CoreDomUtils.showDeleteConfirm('addon.mod_forum.deletesure');
@ -290,6 +294,8 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges
} }
this.scrollToForm(); 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.scrollToForm();
this.analyticsLogEvent('mod_forum_update_discussion_post', `/mod/forum/post.php?edit=${this.post.id}`);
} catch { } catch {
// Cancelled. // 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 { AddonModForumHelper } from '../../services/forum-helper';
import { AddonModForumOffline } from '../../services/forum-offline'; import { AddonModForumOffline } from '../../services/forum-offline';
import { AddonModForumSync, AddonModForumSyncProvider } from '../../services/forum-sync'; import { AddonModForumSync, AddonModForumSyncProvider } from '../../services/forum-sync';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
type SortType = 'flat-newest' | 'flat-oldest' | 'nested'; type SortType = 'flat-newest' | 'flat-oldest' | 'nested';
@ -562,19 +563,7 @@ export class AddonModForumDiscussionPage implements OnInit, AfterViewInit, OnDes
if (forceMarkAsRead || (hasUnreadPosts && this.trackPosts)) { if (forceMarkAsRead || (hasUnreadPosts && this.trackPosts)) {
// Add log in Moodle and mark unread posts as readed. // Add log in Moodle and mark unread posts as readed.
AddonModForum.logDiscussionView(this.discussionId, this.forumId || -1, this.forum.name).catch(() => { this.logDiscussionView(forceMarkAsRead);
// 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());
});
} }
} }
} }
@ -854,6 +843,35 @@ export class AddonModForumDiscussionPage implements OnInit, AfterViewInit, OnDes
return posts; 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 { ActivatedRoute, ActivatedRouteSnapshot } from '@angular/router';
import { AddonModForumDiscussionsSource } from '../../classes/forum-discussions-source'; import { AddonModForumDiscussionsSource } from '../../classes/forum-discussions-source';
import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker'; import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker';
import { CoreTime } from '@singletons/time';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
type NewDiscussionData = { type NewDiscussionData = {
subject: string; subject: string;
@ -105,8 +107,19 @@ export class AddonModForumNewDiscussionPage implements OnInit, OnDestroy, CanLea
protected originalData?: Partial<NewDiscussionData>; protected originalData?: Partial<NewDiscussionData>;
protected forceLeave = false; protected forceLeave = false;
protected initialGroupId?: number; 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 * @inheritdoc
@ -309,6 +322,8 @@ export class AddonModForumNewDiscussionPage implements OnInit, OnDestroy, CanLea
} }
this.showForm = true; this.showForm = true;
this.logView();
} catch (error) { } catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'addon.mod_forum.errorgetgroups', true); CoreDomUtils.showErrorModalDefault(error, 'addon.mod_forum.errorgetgroups', true);

View File

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

View File

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

View File

@ -40,6 +40,7 @@ import {
} from '../../services/glossary'; } from '../../services/glossary';
import { AddonModGlossaryHelper } from '../../services/glossary-helper'; import { AddonModGlossaryHelper } from '../../services/glossary-helper';
import { AddonModGlossaryOffline } from '../../services/glossary-offline'; import { AddonModGlossaryOffline } from '../../services/glossary-offline';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
/** /**
* Page that displays the edit form. * Page that displays the edit form.
@ -76,6 +77,7 @@ export class AddonModGlossaryEditPage implements OnInit, CanLeave {
originalData?: AddonModGlossaryFormData; originalData?: AddonModGlossaryFormData;
protected entry?: AddonModGlossaryEntry;
protected syncId?: string; protected syncId?: string;
protected syncObserver?: CoreEventObserver; protected syncObserver?: CoreEventObserver;
protected isDestroyed = false; protected isDestroyed = false;
@ -99,6 +101,7 @@ export class AddonModGlossaryEditPage implements OnInit, CanLeave {
} else if (entrySlug) { } else if (entrySlug) {
const { entry } = await AddonModGlossary.getEntry(Number(entrySlug)); const { entry } = await AddonModGlossary.getEntry(Number(entrySlug));
this.entry = entry;
this.editorExtraParams.timecreated = entry.timecreated; this.editorExtraParams.timecreated = entry.timecreated;
this.handler = new AddonModGlossaryOnlineFormHandler(this, entry); this.handler = new AddonModGlossaryOnlineFormHandler(this, entry);
} else { } else {
@ -127,6 +130,18 @@ export class AddonModGlossaryEditPage implements OnInit, CanLeave {
await this.handler.loadData(this.glossary); await this.handler.loadData(this.glossary);
this.loaded = true; 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) { } catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'addon.mod_glossary.errorloadingglossary', true); CoreDomUtils.showErrorModalDefault(error, 'addon.mod_glossary.errorloadingglossary', true);

View File

@ -39,6 +39,8 @@ import {
AddonModGlossaryProvider, AddonModGlossaryProvider,
GLOSSARY_ENTRY_UPDATED, GLOSSARY_ENTRY_UPDATED,
} from '../../services/glossary'; } from '../../services/glossary';
import { CoreTime } from '@singletons/time';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
/** /**
* Page that displays a glossary entry. * Page that displays a glossary entry.
@ -70,7 +72,19 @@ export class AddonModGlossaryEntryPage implements OnInit, OnDestroy {
courseId!: number; courseId!: number;
cmId!: 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 { get entry(): AddonModGlossaryEntry | AddonModGlossaryOfflineEntry | undefined {
return this.onlineEntry ?? this.offlineEntry; return this.onlineEntry ?? this.offlineEntry;
@ -128,12 +142,6 @@ export class AddonModGlossaryEntryPage implements OnInit, OnDestroy {
try { try {
if (onlineEntryId) { if (onlineEntryId) {
await this.loadOnlineEntry(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) { } else if (offlineEntryTimeCreated) {
await this.loadOfflineEntry(offlineEntryTimeCreated); await this.loadOfflineEntry(offlineEntryTimeCreated);
} }
@ -161,6 +169,12 @@ export class AddonModGlossaryEntryPage implements OnInit, OnDestroy {
* Delete entry. * Delete entry.
*/ */
async deleteEntry(): Promise<void> { 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 glossaryId = this.glossary?.id;
const cancelled = await CoreUtils.promiseFails( const cancelled = await CoreUtils.promiseFails(
CoreDomUtils.showConfirm(Translate.instant('addon.mod_glossary.areyousuredelete')), CoreDomUtils.showConfirm(Translate.instant('addon.mod_glossary.areyousuredelete')),
@ -250,6 +264,8 @@ export class AddonModGlossaryEntryPage implements OnInit, OnDestroy {
this.canEdit = canUpdateEntries && !!result.permissions?.canupdate; this.canEdit = canUpdateEntries && !!result.permissions?.canupdate;
await this.loadGlossary(); await this.loadGlossary();
this.logView();
} catch (error) { } catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'addon.mod_glossary.errorloadingentry', true); CoreDomUtils.showErrorModalDefault(error, 'addon.mod_glossary.errorloadingentry', true);
} }
@ -321,6 +337,26 @@ export class AddonModGlossaryEntryPage implements OnInit, OnDestroy {
AddonModGlossary.invalidateEntry(this.onlineEntry.id); 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 glossaryId Glossary ID.
* @param mode The mode in which the glossary was viewed. * @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. * @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 = { const params: AddonModGlossaryViewGlossaryWSParams = {
id: glossaryId, id: glossaryId,
mode: mode, mode: mode,
}; };
await CoreCourseLogHelper.logSingle( await CoreCourseLogHelper.log(
'mod_glossary_view_glossary', 'mod_glossary_view_glossary',
params, params,
AddonModGlossaryProvider.COMPONENT, AddonModGlossaryProvider.COMPONENT,
glossaryId, glossaryId,
name,
'glossary',
{ mode },
siteId, siteId,
); );
} }
@ -1046,22 +1042,18 @@ export class AddonModGlossaryProvider {
* *
* @param entryId Entry ID. * @param entryId Entry ID.
* @param glossaryId Glossary ID. * @param glossaryId Glossary ID.
* @param name Name of the glossary.
* @param siteId Site ID. If not defined, current site. * @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 = { const params: AddonModGlossaryViewEntryWSParams = {
id: entryId, id: entryId,
}; };
await CoreCourseLogHelper.logSingle( await CoreCourseLogHelper.log(
'mod_glossary_view_entry', 'mod_glossary_view_entry',
params, params,
AddonModGlossaryProvider.COMPONENT, AddonModGlossaryProvider.COMPONENT,
glossaryId, glossaryId,
name,
'glossary',
{ entryid: entryId },
siteId, siteId,
); );
} }

View File

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

View File

@ -25,6 +25,8 @@ import {
AddonModH5PActivityData, AddonModH5PActivityData,
AddonModH5PActivityAttemptResults, AddonModH5PActivityAttemptResults,
} from '../../services/h5pactivity'; } from '../../services/h5pactivity';
import { CoreTime } from '@singletons/time';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
/** /**
* Page that displays results of an attempt. * Page that displays results of an attempt.
@ -45,7 +47,28 @@ export class AddonModH5PActivityAttemptResultsPage implements OnInit {
cmId!: number; cmId!: number;
protected attemptId!: 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 * @inheritdoc
@ -92,14 +115,7 @@ export class AddonModH5PActivityAttemptResultsPage implements OnInit {
await this.fetchUserProfile(); await this.fetchUserProfile();
if (!this.fetchSuccess) { this.logView();
this.fetchSuccess = true;
CoreUtils.ignoreErrors(AddonModH5PActivity.logViewReport(
this.h5pActivity.id,
this.h5pActivity.name,
{ attemptId: this.attemptId },
));
}
} catch (error) { } catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'Error loading attempt.'); CoreDomUtils.showErrorModalDefault(error, 'Error loading attempt.');
} finally { } finally {

View File

@ -26,6 +26,8 @@ import {
AddonModH5PActivityData, AddonModH5PActivityData,
AddonModH5PActivityUserAttempts, AddonModH5PActivityUserAttempts,
} from '../../services/h5pactivity'; } from '../../services/h5pactivity';
import { CoreTime } from '@singletons/time';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
/** /**
* Page that displays user attempts of a certain user. * Page that displays user attempts of a certain user.
@ -46,7 +48,28 @@ export class AddonModH5PActivityUserAttemptsPage implements OnInit {
isCurrentUser = false; isCurrentUser = false;
protected userId!: number; 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 * @inheritdoc
@ -94,14 +117,7 @@ export class AddonModH5PActivityUserAttemptsPage implements OnInit {
this.fetchUserProfile(), this.fetchUserProfile(),
]); ]);
if (!this.fetchSuccess) { this.logView();
this.fetchSuccess = true;
CoreUtils.ignoreErrors(AddonModH5PActivity.logViewReport(
this.h5pActivity.id,
this.h5pActivity.name,
{ userId: this.userId },
));
}
} catch (error) { } catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'Error loading attempts.'); CoreDomUtils.showErrorModalDefault(error, 'Error loading attempts.');
} finally { } finally {

View File

@ -25,6 +25,8 @@ import {
AddonModH5PActivityProvider, AddonModH5PActivityProvider,
AddonModH5PActivityUserAttempts, AddonModH5PActivityUserAttempts,
} from '../../services/h5pactivity'; } 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. * Page that displays all users that can attempt an H5P activity.
@ -45,7 +47,25 @@ export class AddonModH5PActivityUsersAttemptsPage implements OnInit {
canLoadMore = false; canLoadMore = false;
protected page = 0; 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 * @inheritdoc
@ -90,10 +110,7 @@ export class AddonModH5PActivityUsersAttemptsPage implements OnInit {
this.fetchUsers(refresh), this.fetchUsers(refresh),
]); ]);
if (!this.fetchSuccess) { this.logView();
this.fetchSuccess = true;
CoreUtils.ignoreErrors(AddonModH5PActivity.logViewReport(this.h5pActivity.id, this.h5pActivity.name));
}
} catch (error) { } catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'Error loading attempts.'); CoreDomUtils.showErrorModalDefault(error, 'Error loading attempts.');
} finally { } finally {

View File

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

View File

@ -18,6 +18,7 @@ import { CoreCourseContentsPage } from '@features/course/pages/contents/contents
import { CoreCourse } from '@features/course/services/course'; import { CoreCourse } from '@features/course/services/course';
import { CoreNavigator } from '@services/navigator'; import { CoreNavigator } from '@services/navigator';
import { AddonModImscpProvider, AddonModImscp, AddonModImscpTocItem } from '../../services/imscp'; import { AddonModImscpProvider, AddonModImscp, AddonModImscpTocItem } from '../../services/imscp';
import { CoreUtils } from '@services/utils/utils';
/** /**
* Component that displays a IMSCP. * Component that displays a IMSCP.
@ -30,6 +31,7 @@ import { AddonModImscpProvider, AddonModImscp, AddonModImscpTocItem } from '../.
export class AddonModImscpIndexComponent extends CoreCourseModuleMainResourceComponent implements OnInit { export class AddonModImscpIndexComponent extends CoreCourseModuleMainResourceComponent implements OnInit {
component = AddonModImscpProvider.COMPONENT; component = AddonModImscpProvider.COMPONENT;
pluginName = 'imscp';
items: AddonModImscpTocItem[] = []; items: AddonModImscpTocItem[] = [];
hasStarted = false; hasStarted = false;
@ -100,7 +102,9 @@ export class AddonModImscpIndexComponent extends CoreCourseModuleMainResourceCom
* @inheritdoc * @inheritdoc
*/ */
protected async logActivity(): Promise<void> { 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. * Report a IMSCP as being viewed.
* *
* @param id Module ID. * @param id Module ID.
* @param name Name of the imscp.
* @param siteId Site ID. If not defined, current site. * @param siteId Site ID. If not defined, current site.
* @returns Promise resolved when the WS call is successful. * @returns Promise resolved when the WS call is successful.
*/ */
@ -280,14 +279,11 @@ export class AddonModImscpProvider {
imscpid: id, imscpid: id,
}; };
await CoreCourseLogHelper.logSingle( await CoreCourseLogHelper.log(
'mod_imscp_view_imscp', 'mod_imscp_view_imscp',
params, params,
AddonModImscpProvider.COMPONENT, AddonModImscpProvider.COMPONENT,
id, id,
name,
'imscp',
{},
siteId, siteId,
); );
} }

View File

@ -66,7 +66,7 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo
@Input() action?: string; // The "action" to display first. @Input() action?: string; // The "action" to display first.
component = AddonModLessonProvider.COMPONENT; component = AddonModLessonProvider.COMPONENT;
moduleName = 'lesson'; pluginName = 'lesson';
lesson?: AddonModLessonLessonWSData; // The lesson. lesson?: AddonModLessonLessonWSData; // The lesson.
selectedTab?: number; // The initial selected tab. selectedTab?: number; // The initial selected tab.
@ -372,7 +372,16 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo
return; 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. * First tab selected.
*/ */
indexSelected(): void { indexSelected(): void {
const tabHasChanged = this.selectedTab !== 0;
this.selectedTab = 0; this.selectedTab = 0;
if (tabHasChanged) {
this.callAnalyticsLogEvent();
}
} }
/** /**
* Reports tab selected. * Reports tab selected.
*/ */
reportsSelected(): void { reportsSelected(): void {
const tabHasChanged = this.selectedTab !== 1;
this.selectedTab = 1; this.selectedTab = 1;
if (!this.groupInfo) { if (!this.groupInfo) {
@ -449,6 +464,10 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo
CoreDomUtils.showErrorModalDefault(error, 'Error getting report.'); CoreDomUtils.showErrorModalDefault(error, 'Error getting report.');
}); });
} }
if (tabHasChanged) {
this.callAnalyticsLogEvent();
}
} }
/** /**

View File

@ -54,6 +54,7 @@ import {
import { AddonModLessonOffline } from '../../services/lesson-offline'; import { AddonModLessonOffline } from '../../services/lesson-offline';
import { AddonModLessonSync } from '../../services/lesson-sync'; import { AddonModLessonSync } from '../../services/lesson-sync';
import { CoreFormFields, CoreForms } from '@singletons/form'; import { CoreFormFields, CoreForms } from '@singletons/form';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
/** /**
* Page that allows attempting and reviewing a lesson. * 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.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 { } else {
this.showRetake = false; 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, pageId: result.newpageid,
}); });
} }
this.logContinuePageLoaded();
} catch (error) { } catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'Error processing page'); CoreDomUtils.showErrorModalDefault(error, 'Error processing page');
} finally { } finally {

View File

@ -35,6 +35,7 @@ import {
} from '../../services/lesson'; } from '../../services/lesson';
import { AddonModLessonAnswerData, AddonModLessonHelper } from '../../services/lesson-helper'; import { AddonModLessonAnswerData, AddonModLessonHelper } from '../../services/lesson-helper';
import { CoreTime } from '@singletons/time'; import { CoreTime } from '@singletons/time';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
/** /**
* Page that displays a retake made by a certain user. * 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 userId?: number; // User ID to see the retakes.
protected retakeNumber?: number; // Number of the initial retake to see. 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 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 * @inheritdoc
@ -93,6 +99,8 @@ export class AddonModLessonUserRetakePage implements OnInit {
try { try {
await this.setRetake(retakeNumber); await this.setRetake(retakeNumber);
this.performLogView();
} catch (error) { } catch (error) {
this.selectedRetake = this.previousSelectedRetake ?? this.selectedRetake; this.selectedRetake = this.previousSelectedRetake ?? this.selectedRetake;
CoreDomUtils.showErrorModal(CoreUtils.addDataNotDownloadedError(error, 'Error getting attempt.')); CoreDomUtils.showErrorModal(CoreUtils.addDataNotDownloadedError(error, 'Error getting attempt.'));
@ -160,6 +168,8 @@ export class AddonModLessonUserRetakePage implements OnInit {
this.student.profileimageurl = user?.profileimageurl; this.student.profileimageurl = user?.profileimageurl;
await this.setRetake(this.selectedRetake); await this.setRetake(this.selectedRetake);
this.logView();
} catch (error) { } catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'Error getting data.', true); CoreDomUtils.showErrorModalDefault(error, 'Error getting data.', true);
} }
@ -243,6 +253,23 @@ export class AddonModLessonUserRetakePage implements OnInit {
return formattedData; 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 id Module ID.
* @param password Lesson password (if any). * @param password Lesson password (if any).
* @param name Name of the assign.
* @param siteId Site ID. If not defined, current site. * @param siteId Site ID. If not defined, current site.
* @returns Promise resolved when the WS call is successful. * @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 = { const params: AddonModLessonViewLessonWSParams = {
lessonid: id, lessonid: id,
}; };
@ -2964,14 +2963,11 @@ export class AddonModLessonProvider {
params.password = password; params.password = password;
} }
await CoreCourseLogHelper.logSingle( await CoreCourseLogHelper.log(
'mod_lesson_view_lesson', 'mod_lesson_view_lesson',
params, params,
AddonModLessonProvider.COMPONENT, AddonModLessonProvider.COMPONENT,
id, id,
name,
'lesson',
{},
siteId, siteId,
); );
} }

View File

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

View File

@ -22,6 +22,7 @@ import { CoreDomUtils } from '@services/utils/dom';
import { makeSingleton } from '@singletons'; import { makeSingleton } from '@singletons';
import { CoreEvents } from '@singletons/events'; import { CoreEvents } from '@singletons/events';
import { AddonModLti, AddonModLtiLti } from './lti'; import { AddonModLti, AddonModLtiLti } from './lti';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
/** /**
* Service that provides some helper functions for LTI. * Service that provides some helper functions for LTI.
@ -86,7 +87,7 @@ export class AddonModLtiHelperProvider {
const launchData = await AddonModLti.getLtiLaunchData(lti.id); const launchData = await AddonModLti.getLtiLaunchData(lti.id);
// "View" LTI without blocking the UI. // "View" LTI without blocking the UI.
this.logViewAndCheckCompletion(courseId, module, lti.id, lti.name, siteId); this.logViewAndCheckCompletion(courseId, module, lti.id, siteId);
// Launch LTI. // Launch LTI.
return AddonModLti.launch(launchData.endpoint, launchData.parameters); return AddonModLti.launch(launchData.endpoint, launchData.parameters);
@ -111,16 +112,23 @@ export class AddonModLtiHelperProvider {
courseId: number, courseId: number,
module: CoreCourseModuleData, module: CoreCourseModuleData,
ltiId: number, ltiId: number,
name?: string,
siteId?: string, siteId?: string,
): Promise<void> { ): Promise<void> {
try { try {
await AddonModLti.logView(ltiId, name, siteId); await AddonModLti.logView(ltiId,siteId);
CoreCourse.checkModuleCompletion(courseId, module.completiondata); CoreCourse.checkModuleCompletion(courseId, module.completiondata);
} catch { } catch {
// Ignore errors. // 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, ltiid: id,
}; };
return CoreCourseLogHelper.logSingle( return CoreCourseLogHelper.log(
'mod_lti_view_lti', 'mod_lti_view_lti',
params, params,
AddonModLtiProvider.COMPONENT, AddonModLtiProvider.COMPONENT,
id, id,
name,
'lti',
{},
siteId, siteId,
); );
} }

View File

@ -31,6 +31,7 @@ import { AddonModPageHelper } from '../../services/page-helper';
export class AddonModPageIndexComponent extends CoreCourseModuleMainResourceComponent implements OnInit { export class AddonModPageIndexComponent extends CoreCourseModuleMainResourceComponent implements OnInit {
component = AddonModPageProvider.COMPONENT; component = AddonModPageProvider.COMPONENT;
pluginName = 'page';
contents?: string; contents?: string;
displayDescription = false; displayDescription = false;
displayTimemodified = true; displayTimemodified = true;
@ -114,7 +115,9 @@ export class AddonModPageIndexComponent extends CoreCourseModuleMainResourceComp
* @inheritdoc * @inheritdoc
*/ */
protected async logActivity(): Promise<void> { 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. * Report a page as being viewed.
* *
* @param pageid Module ID. * @param pageid Module ID.
* @param name Name of the page.
* @param siteId Site ID. If not defined, current site. * @param siteId Site ID. If not defined, current site.
* @returns Promise resolved when the WS call is successful. * @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 = { const params: AddonModPageViewPageWSParams = {
pageid, pageid,
}; };
return CoreCourseLogHelper.logSingle( return CoreCourseLogHelper.log(
'mod_page_view_page', 'mod_page_view_page',
params, params,
AddonModPageProvider.COMPONENT, AddonModPageProvider.COMPONENT,
pageid, pageid,
name,
'page',
{},
siteId, siteId,
); );
} }

View File

@ -57,7 +57,7 @@ import {
export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComponent implements OnInit, OnDestroy { export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComponent implements OnInit, OnDestroy {
component = AddonModQuizProvider.COMPONENT; component = AddonModQuizProvider.COMPONENT;
moduleName = 'quiz'; pluginName = 'quiz';
quiz?: AddonModQuizQuizData; // The quiz. quiz?: AddonModQuizQuizData; // The quiz.
now?: number; // Current time. now?: number; // Current time.
syncTime?: string; // Last synchronization time. syncTime?: string; // Last synchronization time.
@ -386,7 +386,9 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp
return; // Shouldn't happen. 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 { CoreTime } from '@singletons/time';
import { CoreDirectivesRegistry } from '@singletons/directives-registry'; import { CoreDirectivesRegistry } from '@singletons/directives-registry';
import { CoreWSError } from '@classes/errors/wserror'; import { CoreWSError } from '@classes/errors/wserror';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
/** /**
* Page that allows attempting a quiz. * 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 // @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. // in the LMS. Once the bug has been fixed, this should be reverted.
if (this.isSequential) { if (this.isSequential) {
await CoreUtils.ignoreErrors( await this.logViewPage(page);
AddonModQuiz.logViewAttempt(this.attempt.id, page, this.preflightData, this.offline, this.quiz),
);
} }
const data = await AddonModQuiz.getAttemptData(this.attempt.id, page, this.preflightData, { 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. // Mark the page as viewed.
if (!this.isSequential) { if (!this.isSequential) {
// @todo MOBILE-4350: Undo workaround. // @todo MOBILE-4350: Undo workaround.
CoreUtils.ignoreErrors( await this.logViewPage(page);
AddonModQuiz.logViewAttempt(this.attempt.id, page, this.preflightData, this.offline, this.quiz),
);
} }
// Start looking for changes. // Start looking for changes.
this.autoSave.startCheckChangesProcess(this.quiz, this.attempt, this.preflightData, this.offline); 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. * Refresh attempt data.
*/ */
@ -618,10 +657,7 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave {
this.dueDateWarning = AddonModQuiz.getAttemptDueDateWarning(this.quiz, this.attempt); this.dueDateWarning = AddonModQuiz.getAttemptDueDateWarning(this.quiz, this.attempt);
// Log summary as viewed. this.logViewSummary();
CoreUtils.ignoreErrors(
AddonModQuiz.logViewAttemptSummary(this.attempt.id, this.preflightData, this.quiz.id, this.quiz.name),
);
} }
/** /**

View File

@ -37,6 +37,7 @@ import {
AddonModQuizWSAdditionalData, AddonModQuizWSAdditionalData,
} from '../../services/quiz'; } from '../../services/quiz';
import { AddonModQuizHelper } from '../../services/quiz-helper'; import { AddonModQuizHelper } from '../../services/quiz-helper';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
/** /**
* Page that allows reviewing a quiz attempt. * Page that allows reviewing a quiz attempt.
@ -73,11 +74,15 @@ export class AddonModQuizReviewPage implements OnInit {
protected attemptId!: number; // The attempt being reviewed. protected attemptId!: number; // The attempt being reviewed.
protected currentPage!: number; // The current page being reviewed. protected currentPage!: number; // The current page being reviewed.
protected options?: AddonModQuizCombinedReviewOptions; // Review options. protected options?: AddonModQuizCombinedReviewOptions; // Review options.
protected fetchSuccess = false; protected logView: () => void;
constructor( constructor(
protected elementRef: ElementRef, 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 { try {
await this.loadPage(page); await this.loadPage(page);
this.performLogView(false, { page });
} catch (error) { } catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'addon.mod_quiz.errorgetquestions', true); CoreDomUtils.showErrorModalDefault(error, 'addon.mod_quiz.errorgetquestions', true);
} finally { } finally {
@ -156,12 +163,7 @@ export class AddonModQuizReviewPage implements OnInit {
// Load questions. // Load questions.
await this.loadPage(this.currentPage); await this.loadPage(this.currentPage);
if (!this.fetchSuccess) { this.logView();
this.fetchSuccess = true;
CoreUtils.ignoreErrors(
AddonModQuiz.logViewAttemptReview(this.attemptId, this.quiz.id, this.quiz.name),
);
}
} catch (error) { } catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'addon.mod_quiz.errorgetquiz', true); 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. * Switch mode: all questions in same page OR one page at a time.
*/ */
switchMode(): void { async switchMode(): Promise<void> {
this.showAll = !this.showAll; this.showAll = !this.showAll;
// Load all questions or first page, depending on the mode. // 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> { async openNavigation(): Promise<void> {
@ -351,6 +355,37 @@ export class AddonModQuizReviewPage implements OnInit {
this.changePage(modalData.page, modalData.slot); 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 & { type QuizQuestion = CoreQuestionQuestionParsed & {
readableMark?: string; 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) { if (!finish) {
// Answers sent, now set the current page. // 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( await CoreUtils.ignoreErrors(AddonModQuiz.logViewAttempt(
onlineAttempt.id, onlineAttempt.id,
offlineAttempt.currentpage, offlineAttempt.currentpage,
preflightData, preflightData,
false, false,
undefined,
siteId, siteId,
)); ));
} }

View File

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

View File

@ -47,6 +47,7 @@ import { CorePlatform } from '@services/platform';
export class AddonModResourceIndexComponent extends CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy { export class AddonModResourceIndexComponent extends CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy {
component = AddonModResourceProvider.COMPONENT; component = AddonModResourceProvider.COMPONENT;
pluginName = 'resource';
mode = ''; mode = '';
src = ''; src = '';
@ -188,7 +189,9 @@ export class AddonModResourceIndexComponent extends CoreCourseModuleMainResource
* @inheritdoc * @inheritdoc
*/ */
protected async logActivity(): Promise<void> { 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 { makeSingleton, Translate } from '@singletons';
import { CorePath } from '@singletons/path'; import { CorePath } from '@singletons/path';
import { AddonModResource, AddonModResourceProvider } from './resource'; import { AddonModResource, AddonModResourceProvider } from './resource';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
/** /**
* Service that provides helper functions for resources. * Service that provides helper functions for resources.
@ -206,6 +207,14 @@ export class AddonModResourceHelperProvider {
} catch { } catch {
// Ignore errors. // 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) { } catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'addon.mod_resource.errorwhileloadingthecontent', true); CoreDomUtils.showErrorModalDefault(error, 'addon.mod_resource.errorwhileloadingthecontent', true);
} finally { } finally {

View File

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

View File

@ -57,7 +57,7 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom
@Input() autoPlayData?: AddonModScormAutoPlayData; // Data to use to play the SCORM automatically. @Input() autoPlayData?: AddonModScormAutoPlayData; // Data to use to play the SCORM automatically.
component = AddonModScormProvider.COMPONENT; component = AddonModScormProvider.COMPONENT;
moduleName = 'scorm'; pluginName = 'scorm';
scorm?: AddonModScormScorm; // The SCORM object. scorm?: AddonModScormScorm; // The SCORM object.
currentOrganization: Partial<AddonModScormOrganization> & { identifier: string} = { currentOrganization: Partial<AddonModScormOrganization> & { identifier: string} = {
@ -361,7 +361,9 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom
return; // Shouldn't happen. 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'; } from '../../services/scorm';
import { AddonModScormHelper, AddonModScormTOCScoWithIcon } from '../../services/scorm-helper'; import { AddonModScormHelper, AddonModScormTOCScoWithIcon } from '../../services/scorm-helper';
import { AddonModScormSync } from '../../services/scorm-sync'; import { AddonModScormSync } from '../../services/scorm-sync';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
/** /**
* Page that allows playing a SCORM. * Page that allows playing a SCORM.
@ -442,8 +443,7 @@ export class AddonModScormPlayerPage implements OnInit, OnDestroy {
this.markCompleted(sco); this.markCompleted(sco);
} }
// Trigger SCO launch event. this.logEvent(sco.id);
CoreUtils.ignoreErrors(AddonModScorm.logLaunchSco(this.scorm.id, sco.id, this.scorm.name));
} }
/** /**
@ -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 * @inheritdoc
*/ */

View File

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

View File

@ -38,6 +38,7 @@ import {
AddonModSurveySyncProvider, AddonModSurveySyncProvider,
AddonModSurveySyncResult, AddonModSurveySyncResult,
} from '../../services/survey-sync'; } from '../../services/survey-sync';
import { CoreUtils } from '@services/utils/utils';
/** /**
* Component that displays a survey. * Component that displays a survey.
@ -50,7 +51,7 @@ import {
export class AddonModSurveyIndexComponent extends CoreCourseModuleMainActivityComponent implements OnInit { export class AddonModSurveyIndexComponent extends CoreCourseModuleMainActivityComponent implements OnInit {
component = AddonModSurveyProvider.COMPONENT; component = AddonModSurveyProvider.COMPONENT;
moduleName = 'survey'; pluginName = 'survey';
survey?: AddonModSurveySurvey; survey?: AddonModSurveySurvey;
questions: AddonModSurveyQuestionFormatted[] = []; questions: AddonModSurveyQuestionFormatted[] = [];
@ -168,7 +169,9 @@ export class AddonModSurveyIndexComponent extends CoreCourseModuleMainActivityCo
return; // Shouldn't happen. 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. * Report the survey as being viewed.
* *
* @param id Module ID. * @param id Module ID.
* @param name Name of the assign.
* @param siteId Site ID. If not defined, current site. * @param siteId Site ID. If not defined, current site.
* @returns Promise resolved when the WS call is successful. * @returns Promise resolved when the WS call is successful.
*/ */
@ -217,14 +216,11 @@ export class AddonModSurveyProvider {
surveyid: id, surveyid: id,
}; };
await CoreCourseLogHelper.logSingle( await CoreCourseLogHelper.log(
'mod_survey_view_survey', 'mod_survey_view_survey',
params, params,
AddonModSurveyProvider.COMPONENT, AddonModSurveyProvider.COMPONENT,
id, id,
name,
'survey',
{},
siteId, siteId,
); );
} }

View File

@ -34,6 +34,7 @@ import { AddonModUrlHelper } from '../../services/url-helper';
export class AddonModUrlIndexComponent extends CoreCourseModuleMainResourceComponent implements OnInit { export class AddonModUrlIndexComponent extends CoreCourseModuleMainResourceComponent implements OnInit {
component = AddonModUrlProvider.COMPONENT; component = AddonModUrlProvider.COMPONENT;
pluginName = 'url';
url?: string; url?: string;
name?: string; name?: string;
@ -153,12 +154,14 @@ export class AddonModUrlIndexComponent extends CoreCourseModuleMainResourceCompo
*/ */
protected async logView(): Promise<void> { protected async logView(): Promise<void> {
try { try {
await AddonModUrl.logView(this.module.instance, this.module.name); await AddonModUrl.logView(this.module.instance);
this.checkCompletion(); this.checkCompletion();
} catch { } catch {
// Ignore errors. // 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 { AddonModUrlIndexComponent } from '../../components/index/index';
import { AddonModUrl } from '../url'; import { AddonModUrl } from '../url';
import { AddonModUrlHelper } from '../url-helper'; import { AddonModUrlHelper } from '../url-helper';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
/** /**
* Handler to support url modules. * Handler to support url modules.
@ -64,14 +65,7 @@ export class AddonModUrlModuleHandlerService extends CoreModuleHandlerBase imple
* @param courseId The course ID. * @param courseId The course ID.
*/ */
const openUrl = async (module: CoreCourseModuleData, courseId: number): Promise<void> => { const openUrl = async (module: CoreCourseModuleData, courseId: number): Promise<void> => {
try { await this.logView(module);
if (module.instance) {
await AddonModUrl.logView(module.instance, module.name);
CoreCourse.checkModuleCompletion(module.course, module.completiondata);
}
} catch {
// Ignore errors.
}
CoreCourse.storeModuleViewed(courseId, module.id); CoreCourse.storeModuleViewed(courseId, module.id);
@ -196,5 +190,27 @@ export class AddonModUrlModuleHandlerService extends CoreModuleHandlerBase imple
return !iconUrl?.startsWith('assets/img/files/'); 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); export const AddonModUrlModuleHandler = makeSingleton(AddonModUrlModuleHandlerService);

View File

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

View File

@ -75,7 +75,7 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp
component = AddonModWikiProvider.COMPONENT; component = AddonModWikiProvider.COMPONENT;
componentId?: number; componentId?: number;
moduleName = 'wiki'; pluginName = 'wiki';
groupWiki = false; groupWiki = false;
isOnline = false; isOnline = false;
@ -327,9 +327,7 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp
await this.showLoadingAndFetch(true, false); await this.showLoadingAndFetch(true, false);
if (this.currentPage && this.wiki) { this.currentPage && this.logPageViewed(this.currentPage);
CoreUtils.ignoreErrors(AddonModWiki.logPageView(this.currentPage, this.wiki.id, this.wiki.name));
}
}, CoreSites.getCurrentSiteId()); }, CoreSites.getCurrentSiteId());
} }
@ -443,12 +441,60 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp
return; // Shouldn't happen. return; // Shouldn't happen.
} }
if (!this.pageId) { if (this.pageId) {
await AddonModWiki.logView(this.wiki.id, this.wiki.name); // View page.
} else {
this.checkCompletionAfterLog = false; 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(), homeView: this.getWikiHomeView(),
moduleId: this.module.id, moduleId: this.module.id,
courseId: this.courseId, courseId: this.courseId,
selectedId: this.currentPage,
selectedTitle: this.currentPageObj && this.currentPageObj.title, selectedTitle: this.currentPageObj && this.currentPageObj.title,
wiki: this.wiki,
}, },
}); });

View File

@ -15,7 +15,8 @@
import { Component, Input, OnInit } from '@angular/core'; import { Component, Input, OnInit } from '@angular/core';
import { ModalController } from '@singletons'; import { ModalController } from '@singletons';
import { AddonModWikiPageDBRecord } from '../../services/database/wiki'; 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. * Modal to display the map of a Wiki.
@ -27,6 +28,8 @@ import { AddonModWikiSubwikiPage } from '../../services/wiki';
export class AddonModWikiMapModalComponent implements OnInit { export class AddonModWikiMapModalComponent implements OnInit {
@Input() pages: (AddonModWikiSubwikiPage | AddonModWikiPageDBRecord)[] = []; @Input() pages: (AddonModWikiSubwikiPage | AddonModWikiPageDBRecord)[] = [];
@Input() wiki?: AddonModWikiWiki;
@Input() selectedId?: number;
@Input() selectedTitle?: string; @Input() selectedTitle?: string;
@Input() moduleId?: number; @Input() moduleId?: number;
@Input() courseId?: number; @Input() courseId?: number;
@ -39,6 +42,16 @@ export class AddonModWikiMapModalComponent implements OnInit {
*/ */
ngOnInit(): void { ngOnInit(): void {
this.constructMap(); 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 { AddonModWiki, AddonModWikiProvider } from '../../services/wiki';
import { AddonModWikiOffline } from '../../services/wiki-offline'; import { AddonModWikiOffline } from '../../services/wiki-offline';
import { AddonModWikiSync } from '../../services/wiki-sync'; import { AddonModWikiSync } from '../../services/wiki-sync';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
/** /**
* Page that allows adding or editing a wiki page. * 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 editOffline = false; // Whether the user is editing an offline page.
protected subwikiFiles: CoreWSFile[] = []; // List of files of the subwiki. protected subwikiFiles: CoreWSFile[] = []; // List of files of the subwiki.
protected originalContent?: string; // The original page content. protected originalContent?: string; // The original page content.
protected originalTitle?: string; // The original page title.
protected version?: number; // Page version. protected version?: number; // Page version.
protected renewLockInterval?: number; // An interval to renew the lock every certain time. protected renewLockInterval?: number; // An interval to renew the lock every certain time.
protected forceLeave = false; // To allow leaving the page without checking for changes. 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.groupId = CoreNavigator.getRouteNumberParam('groupId');
this.userId = CoreNavigator.getRouteNumberParam('userId'); this.userId = CoreNavigator.getRouteNumberParam('userId');
let pageTitle = CoreNavigator.getRouteParam<string>('pageTitle'); const pageTitle = CoreNavigator.getRouteParam<string>('pageTitle');
pageTitle = pageTitle ? CoreTextUtils.cleanTags(pageTitle.replace(/\+/g, ' '), { singleLine: true }) : ''; this.originalTitle = pageTitle ? CoreTextUtils.cleanTags(pageTitle.replace(/\+/g, ' '), { singleLine: true }) : '';
this.canEditTitle = !pageTitle; this.canEditTitle = !this.originalTitle;
this.title = pageTitle ? this.title = this.originalTitle ?
Translate.instant('addon.mod_wiki.editingpage', { $a: pageTitle }) : Translate.instant('addon.mod_wiki.editingpage', { $a: this.originalTitle }) :
Translate.instant('addon.mod_wiki.newpagehdr'); Translate.instant('addon.mod_wiki.newpagehdr');
this.blockId = AddonModWikiSync.getSubwikiBlockId(this.subwikiId, this.wikiId, this.userId, this.groupId); this.blockId = AddonModWikiSync.getSubwikiBlockId(this.subwikiId, this.wikiId, this.userId, this.groupId);
// Create the form group and its controls. // 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); this.pageForm.addControl('text', this.contentControl);
// Block the wiki so it cannot be synced. // Block the wiki so it cannot be synced.
@ -111,8 +113,8 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy, CanLeave {
if (this.section) { if (this.section) {
this.editorExtraParams.section = this.section; this.editorExtraParams.section = this.section;
} }
} else if (pageTitle) { } else if (this.originalTitle) {
this.editorExtraParams.pagetitle = pageTitle; this.editorExtraParams.pagetitle = this.originalTitle;
} }
try { try {
@ -126,6 +128,8 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy, CanLeave {
this.blockId = newBlockId; this.blockId = newBlockId;
CoreSync.blockOperation(this.component, this.blockId); CoreSync.blockOperation(this.component, this.blockId);
} }
this.logView();
} }
} finally { } finally {
this.loaded = true; this.loaded = true;
@ -158,6 +162,7 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy, CanLeave {
this.wikiId = pageContents.wikiid; this.wikiId = pageContents.wikiid;
this.subwikiId = pageContents.subwikiid; this.subwikiId = pageContents.subwikiid;
this.title = Translate.instant('addon.mod_wiki.editingpage', { $a: pageContents.title }); this.title = Translate.instant('addon.mod_wiki.editingpage', { $a: pageContents.title });
this.originalTitle = pageContents.title;
this.groupId = pageContents.groupid; this.groupId = pageContents.groupid;
this.userId = pageContents.userid; this.userId = pageContents.userid;
canEdit = pageContents.caneditpage; 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 * @inheritdoc
*/ */

View File

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

View File

@ -66,7 +66,7 @@ export class AddonModWorkshopIndexComponent extends CoreCourseModuleMainActivity
@Input() group = 0; @Input() group = 0;
component = AddonModWorkshopProvider.COMPONENT; component = AddonModWorkshopProvider.COMPONENT;
moduleName = 'workshop'; pluginName = 'workshop';
workshop?: AddonModWorkshopData; workshop?: AddonModWorkshopData;
page = 0; page = 0;
@ -255,7 +255,9 @@ export class AddonModWorkshopIndexComponent extends CoreCourseModuleMainActivity
return; // Shouldn't happen. 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 { AddonModWorkshopHelper, AddonModWorkshopSubmissionAssessmentWithFormData } from '../../services/workshop-helper';
import { AddonModWorkshopOffline } from '../../services/workshop-offline'; import { AddonModWorkshopOffline } from '../../services/workshop-offline';
import { AddonModWorkshopSyncProvider } from '../../services/workshop-sync'; import { AddonModWorkshopSyncProvider } from '../../services/workshop-sync';
import { CoreTime } from '@singletons/time';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
/** /**
* Page that displays a workshop assessment. * Page that displays a workshop assessment.
@ -89,6 +91,7 @@ export class AddonModWorkshopAssessmentPage implements OnInit, OnDestroy, CanLea
protected siteId: string; protected siteId: string;
protected currentUserId: number; protected currentUserId: number;
protected forceLeave = false; protected forceLeave = false;
protected logView: () => void;
constructor( constructor(
protected fb: FormBuilder, protected fb: FormBuilder,
@ -111,6 +114,20 @@ export class AddonModWorkshopAssessmentPage implements OnInit, OnDestroy, CanLea
this.refreshAllData(); this.refreshAllData();
} }
}, this.siteId); }, 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'; } from '../../services/workshop';
import { AddonModWorkshopHelper, AddonModWorkshopSubmissionDataWithOfflineData } from '../../services/workshop-helper'; import { AddonModWorkshopHelper, AddonModWorkshopSubmissionDataWithOfflineData } from '../../services/workshop-helper';
import { AddonModWorkshopOffline } from '../../services/workshop-offline'; import { AddonModWorkshopOffline } from '../../services/workshop-offline';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
/** /**
* Page that displays the workshop edit submission. * Page that displays the workshop edit submission.
@ -224,6 +225,8 @@ export class AddonModWorkshopEditSubmissionPage implements OnInit, OnDestroy, Ca
} }
this.loaded = true; this.loaded = true;
this.logView();
} catch (error) { } catch (error) {
this.loaded = false; 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. * Force leaving the page, without checking for changes.
*/ */

View File

@ -47,6 +47,8 @@ import {
} from '../../services/workshop-helper'; } from '../../services/workshop-helper';
import { AddonModWorkshopOffline } from '../../services/workshop-offline'; import { AddonModWorkshopOffline } from '../../services/workshop-offline';
import { AddonModWorkshopSyncProvider, AddonModWorkshopAutoSyncData } from '../../services/workshop-sync'; import { AddonModWorkshopSyncProvider, AddonModWorkshopAutoSyncData } from '../../services/workshop-sync';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
import { CoreTime } from '@singletons/time';
/** /**
* Page that displays a workshop submission. * Page that displays a workshop submission.
@ -102,7 +104,7 @@ export class AddonModWorkshopSubmissionPage implements OnInit, OnDestroy, CanLea
protected obsAssessmentSaved: CoreEventObserver; protected obsAssessmentSaved: CoreEventObserver;
protected syncObserver: CoreEventObserver; protected syncObserver: CoreEventObserver;
protected isDestroyed = false; protected isDestroyed = false;
protected fetchSuccess = false; protected logView: () => void;
constructor( constructor(
protected fb: FormBuilder, protected fb: FormBuilder,
@ -125,6 +127,8 @@ export class AddonModWorkshopSubmissionPage implements OnInit, OnDestroy, CanLea
// Update just when all database is synced. // Update just when all database is synced.
this.eventReceived(data); this.eventReceived(data);
}, this.siteId); }, this.siteId);
this.logView = CoreTime.once(() => this.performLogView());
} }
/** /**
@ -599,19 +603,21 @@ export class AddonModWorkshopSubmissionPage implements OnInit, OnDestroy, CanLea
/** /**
* Log submission viewed. * Log submission viewed.
*/ */
protected async logView(): Promise<void> { protected async performLogView(): Promise<void> {
if (this.fetchSuccess) {
return; // Already done.
}
this.fetchSuccess = true;
try { 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); CoreCourse.checkModuleCompletion(this.courseId, this.module.completiondata);
} catch { } catch {
// Ignore errors. // 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. * Report the workshop as being viewed.
* *
* @param id Workshop ID. * @param id Workshop ID.
* @param name Name of the workshop.
* @param siteId Site ID. If not defined, current site. * @param siteId Site ID. If not defined, current site.
* @returns Promise resolved when the WS call is successful. * @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 = { const params: AddonModWorkshopViewWorkshopWSParams = {
workshopid: id, workshopid: id,
}; };
await CoreCourseLogHelper.logSingle( await CoreCourseLogHelper.log(
'mod_workshop_view_workshop', 'mod_workshop_view_workshop',
params, params,
AddonModWorkshopProvider.COMPONENT, AddonModWorkshopProvider.COMPONENT,
id, id,
name,
'workshop',
{},
siteId, siteId,
); );
} }
@ -1469,23 +1465,19 @@ export class AddonModWorkshopProvider {
* *
* @param id Submission ID. * @param id Submission ID.
* @param workshopId Workshop ID. * @param workshopId Workshop ID.
* @param name Name of the workshop.
* @param siteId Site ID. If not defined, current site. * @param siteId Site ID. If not defined, current site.
* @returns Promise resolved when the WS call is successful. * @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 = { const params: AddonModWorkshopViewSubmissionWSParams = {
submissionid: id, submissionid: id,
}; };
await CoreCourseLogHelper.logSingle( await CoreCourseLogHelper.log(
'mod_workshop_view_submission', 'mod_workshop_view_submission',
params, params,
AddonModWorkshopProvider.COMPONENT, AddonModWorkshopProvider.COMPONENT,
workshopId, workshopId,
name,
'workshop',
params,
siteId, siteId,
); );
} }

View File

@ -21,12 +21,16 @@ import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { CoreAnimations } from '@components/animations'; import { CoreAnimations } from '@components/animations';
import { CoreUser, CoreUserProfile } from '@features/user/services/user'; import { CoreUser, CoreUserProfile } from '@features/user/services/user';
import { IonContent, IonRefresher } from '@ionic/angular'; import { IonContent, IonRefresher } from '@ionic/angular';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
import { CoreNavigator } from '@services/navigator'; import { CoreNavigator } from '@services/navigator';
import { CoreSites } from '@services/sites'; import { CoreSites } from '@services/sites';
import { CoreDomUtils, ToastDuration } from '@services/utils/dom'; import { CoreDomUtils, ToastDuration } from '@services/utils/dom';
import { CoreTextUtils } from '@services/utils/text'; import { CoreTextUtils } from '@services/utils/text';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreUtils } from '@services/utils/utils'; import { CoreUtils } from '@services/utils/utils';
import { Translate } from '@singletons';
import { CoreEventObserver, CoreEvents } from '@singletons/events'; import { CoreEventObserver, CoreEvents } from '@singletons/events';
import { CoreTime } from '@singletons/time';
/** /**
* Page that displays a list of notes. * Page that displays a list of notes.
@ -54,9 +58,11 @@ export class AddonNotesListPage implements OnInit, OnDestroy {
currentUserId!: number; currentUserId!: number;
protected syncObserver!: CoreEventObserver; protected syncObserver!: CoreEventObserver;
protected logAfterFetch = true; protected logView: () => void;
constructor() { constructor() {
this.logView = CoreTime.once(() => this.performLogView());
try { try {
this.courseId = CoreNavigator.getRequiredRouteNumberParam('courseId'); this.courseId = CoreNavigator.getRequiredRouteNumberParam('courseId');
this.userId = CoreNavigator.getRouteNumberParam('userId'); this.userId = CoreNavigator.getRouteNumberParam('userId');
@ -128,10 +134,7 @@ export class AddonNotesListPage implements OnInit, OnDestroy {
this.notes = await AddonNotes.getNotesUserData(notesList); this.notes = await AddonNotes.getNotesUserData(notesList);
} }
if (this.logAfterFetch) { this.logView();
this.logAfterFetch = false;
CoreUtils.ignoreErrors(AddonNotes.logView(this.courseId, this.userId));
}
} catch (error) { } catch (error) {
CoreDomUtils.showErrorModal(error); CoreDomUtils.showErrorModal(error);
} finally { } finally {
@ -176,7 +179,6 @@ export class AddonNotesListPage implements OnInit, OnDestroy {
this.notesLoaded = false; this.notesLoaded = false;
this.refreshIcon = CoreConstants.ICON_LOADING; this.refreshIcon = CoreConstants.ICON_LOADING;
this.syncIcon = CoreConstants.ICON_LOADING; this.syncIcon = CoreConstants.ICON_LOADING;
this.logAfterFetch = true;
await this.fetchNotes(true); await this.fetchNotes(true);
} }
@ -190,6 +192,8 @@ export class AddonNotesListPage implements OnInit, OnDestroy {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
this.logViewAdd();
const modalData = await CoreDomUtils.openModal<AddonNotesAddModalReturn>({ const modalData = await CoreDomUtils.openModal<AddonNotesAddModalReturn>({
component: AddonNotesAddComponent, component: AddonNotesAddComponent,
componentProps: { componentProps: {
@ -225,6 +229,8 @@ export class AddonNotesListPage implements OnInit, OnDestroy {
e.stopPropagation(); e.stopPropagation();
try { try {
this.logViewDelete(note);
await CoreDomUtils.showDeleteConfirm('addon.notes.deleteconfirm'); await CoreDomUtils.showDeleteConfirm('addon.notes.deleteconfirm');
try { try {
await AddonNotes.deleteNote(note, this.courseId); 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. * Page destroyed.
*/ */

View File

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

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