MOBILE-3659 course: Implement log helper
parent
8d752d2bf5
commit
2906210242
|
@ -17,15 +17,15 @@ import { NgModule } from '@angular/core';
|
|||
import { CORE_SITE_SCHEMAS } from '@services/sites';
|
||||
|
||||
import { SITE_SCHEMA, OFFLINE_SITE_SCHEMA } from './services/database/course';
|
||||
import { SITE_SCHEMA as LOG_SITE_SCHEMA } from './services/database/log';
|
||||
|
||||
@NgModule({
|
||||
providers: [
|
||||
{
|
||||
provide: CORE_SITE_SCHEMAS,
|
||||
useValue: [SITE_SCHEMA, OFFLINE_SITE_SCHEMA],
|
||||
useValue: [SITE_SCHEMA, OFFLINE_SITE_SCHEMA, LOG_SITE_SCHEMA],
|
||||
multi: true,
|
||||
},
|
||||
],
|
||||
})
|
||||
export class CoreCourseModule {
|
||||
}
|
||||
export class CoreCourseModule {}
|
||||
|
|
|
@ -47,8 +47,9 @@ export interface CoreCourseOptionsHandler extends CoreDelegateHandler {
|
|||
* @param admOptions Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions.
|
||||
* @return True or promise resolved with true if enabled.
|
||||
*/
|
||||
isEnabledForCourse(courseId: number,
|
||||
accessData: CoreCourseAccessData,
|
||||
isEnabledForCourse(
|
||||
courseId: number,
|
||||
accessData: CoreCourseAccess,
|
||||
navOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
||||
admOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
||||
): boolean | Promise<boolean>;
|
||||
|
@ -56,7 +57,7 @@ export interface CoreCourseOptionsHandler extends CoreDelegateHandler {
|
|||
/**
|
||||
* Returns the data needed to render the handler.
|
||||
*
|
||||
* @param course The course. // @todo: define type in the whole file.
|
||||
* @param course The course.
|
||||
* @return Data or promise resolved with the data.
|
||||
*/
|
||||
getDisplayData?(
|
||||
|
@ -226,7 +227,7 @@ export class CoreCourseOptionsDelegateService extends CoreDelegate<CoreCourseOpt
|
|||
|
||||
protected coursesHandlers: {
|
||||
[courseId: number]: {
|
||||
access: any;
|
||||
access: CoreCourseAccess;
|
||||
navOptions?: CoreCourseUserAdminOrNavOptionIndexed;
|
||||
admOptions?: CoreCourseUserAdminOrNavOptionIndexed;
|
||||
deferred: PromiseDefer<void>;
|
||||
|
@ -320,7 +321,7 @@ export class CoreCourseOptionsDelegateService extends CoreDelegate<CoreCourseOpt
|
|||
protected async getHandlersForAccess(
|
||||
courseId: number,
|
||||
refresh: boolean,
|
||||
accessData: any,
|
||||
accessData: CoreCourseAccess,
|
||||
navOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
||||
admOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
||||
): Promise<CoreCourseOptionsHandler[]> {
|
||||
|
@ -618,7 +619,7 @@ export class CoreCourseOptionsDelegateService extends CoreDelegate<CoreCourseOpt
|
|||
*/
|
||||
async updateHandlersForCourse(
|
||||
courseId: number,
|
||||
accessData: any,
|
||||
accessData: CoreCourseAccess,
|
||||
navOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
||||
admOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
||||
): Promise<void> {
|
||||
|
@ -673,5 +674,6 @@ export class CoreCourseOptionsDelegateService extends CoreDelegate<CoreCourseOpt
|
|||
|
||||
export class CoreCourseOptionsDelegate extends makeSingleton(CoreCourseOptionsDelegateService) {}
|
||||
|
||||
// @todo define
|
||||
export type CoreCourseAccessData = any;
|
||||
export type CoreCourseAccess = {
|
||||
type: string; // Either CoreCourseProvider.ACCESS_GUEST or CoreCourseProvider.ACCESS_DEFAULT.
|
||||
};
|
||||
|
|
|
@ -856,7 +856,6 @@ export class CoreCourseProvider {
|
|||
* @param siteId Site ID. If not defined, current site.
|
||||
* @param name Name of the course.
|
||||
* @return Promise resolved when the WS call is successful.
|
||||
* @todo use logHelper. Remove eslint disable when done.
|
||||
*/
|
||||
async logView(courseId: number, sectionNumber?: number, siteId?: string, name?: string): Promise<void> {
|
||||
const params: CoreCourseViewCourseWSParams = {
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { CoreSiteSchema } from '@services/sites';
|
||||
|
||||
/**
|
||||
* Database variables for CoreCourse service.
|
||||
*/
|
||||
export const ACTIVITY_LOG_TABLE = 'course_activity_log';
|
||||
export const SITE_SCHEMA: CoreSiteSchema = {
|
||||
name: 'CoreCourseLogHelperProvider',
|
||||
version: 1,
|
||||
tables: [
|
||||
{
|
||||
name: ACTIVITY_LOG_TABLE,
|
||||
columns: [
|
||||
{
|
||||
name: 'component',
|
||||
type: 'TEXT',
|
||||
},
|
||||
{
|
||||
name: 'componentid',
|
||||
type: 'INTEGER',
|
||||
},
|
||||
{
|
||||
name: 'ws',
|
||||
type: 'TEXT',
|
||||
},
|
||||
{
|
||||
name: 'data',
|
||||
type: 'TEXT',
|
||||
},
|
||||
{
|
||||
name: 'time',
|
||||
type: 'INTEGER',
|
||||
}
|
||||
],
|
||||
primaryKeys: ['component', 'componentid', 'ws', 'time'],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export type CoreCourseActivityLogDBRecord = {
|
||||
component: string;
|
||||
componentid: number;
|
||||
ws: string;
|
||||
time: number;
|
||||
data?: string;
|
||||
};
|
|
@ -0,0 +1,365 @@
|
|||
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { CoreApp } from '@services/app';
|
||||
import { CoreSites } from '@services/sites';
|
||||
import { CoreTextUtils } from '@services/utils/text';
|
||||
import { CoreTimeUtils } from '@services/utils/time';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { CorePushNotifications } from '@features/pushnotifications/services/pushnotifications';
|
||||
import { makeSingleton } from '@singletons';
|
||||
import { ACTIVITY_LOG_TABLE, CoreCourseActivityLogDBRecord } from './database/log';
|
||||
import { CoreStatusWithWarningsWSResponse } from '@services/ws';
|
||||
import { CoreWSError } from '@classes/errors/wserror';
|
||||
|
||||
/**
|
||||
* Helper to manage logging to Moodle.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class CoreCourseLogHelperProvider {
|
||||
|
||||
/**
|
||||
* Delete the offline saved activity logs.
|
||||
*
|
||||
* @param component Component name.
|
||||
* @param componentId Component ID.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved when deleted, rejected if failure.
|
||||
*/
|
||||
protected async deleteLogs(component: string, componentId: number, siteId?: string): Promise<void> {
|
||||
const site = await CoreSites.instance.getSite(siteId);
|
||||
|
||||
const conditions: Partial<CoreCourseActivityLogDBRecord> = {
|
||||
component,
|
||||
componentid: componentId,
|
||||
};
|
||||
|
||||
await site.getDb().deleteRecords(ACTIVITY_LOG_TABLE, conditions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a WS based log.
|
||||
*
|
||||
* @param component Component name.
|
||||
* @param componentId Component ID.
|
||||
* @param ws WS name.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved when deleted, rejected if failure.
|
||||
*/
|
||||
protected async deleteWSLogsByComponent(component: string, componentId: number, ws: string, siteId?: string): Promise<void> {
|
||||
const site = await CoreSites.instance.getSite(siteId);
|
||||
|
||||
const conditions: Partial<CoreCourseActivityLogDBRecord> = {
|
||||
component,
|
||||
componentid: componentId,
|
||||
ws,
|
||||
};
|
||||
|
||||
await site.getDb().deleteRecords(ACTIVITY_LOG_TABLE, conditions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the offline saved activity logs using call data.
|
||||
*
|
||||
* @param ws WS name.
|
||||
* @param data Data to send to the WS.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved when deleted, rejected if failure.
|
||||
*/
|
||||
protected async deleteWSLogs(ws: string, data: Record<string, unknown>, siteId?: string): Promise<void> {
|
||||
const site = await CoreSites.instance.getSite(siteId);
|
||||
|
||||
const conditions: Partial<CoreCourseActivityLogDBRecord> = {
|
||||
ws,
|
||||
data: CoreUtils.instance.sortAndStringify(data),
|
||||
};
|
||||
|
||||
await site.getDb().deleteRecords(ACTIVITY_LOG_TABLE, conditions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the offline saved activity logs.
|
||||
*
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved with the list of offline logs.
|
||||
*/
|
||||
protected async getAllLogs(siteId?: string): Promise<CoreCourseActivityLogDBRecord[]> {
|
||||
const site = await CoreSites.instance.getSite(siteId);
|
||||
|
||||
return site.getDb().getAllRecords<CoreCourseActivityLogDBRecord>(ACTIVITY_LOG_TABLE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the offline saved activity logs.
|
||||
*
|
||||
* @param component Component name.
|
||||
* @param componentId Component ID.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved with the list of offline logs.
|
||||
*/
|
||||
protected async getLogs(component: string, componentId: number, siteId?: string): Promise<CoreCourseActivityLogDBRecord[]> {
|
||||
const site = await CoreSites.instance.getSite(siteId);
|
||||
|
||||
const conditions: Partial<CoreCourseActivityLogDBRecord> = {
|
||||
component,
|
||||
componentid: componentId,
|
||||
};
|
||||
|
||||
return site.getDb().getRecords<CoreCourseActivityLogDBRecord>(ACTIVITY_LOG_TABLE, conditions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform log online. Data will be saved offline for syncing.
|
||||
*
|
||||
* @param ws WS name.
|
||||
* @param data Data to send to the WS.
|
||||
* @param component Component name.
|
||||
* @param componentId Component ID.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
async log(ws: string, data: Record<string, unknown>, component: string, componentId: number, siteId?: string): Promise<void> {
|
||||
const site = await CoreSites.instance.getSite(siteId);
|
||||
|
||||
if (!CoreApp.instance.isOnline()) {
|
||||
// App is offline, store the action.
|
||||
return this.storeOffline(ws, data, component, componentId, site.getId());
|
||||
}
|
||||
|
||||
try {
|
||||
await this.logOnline(ws, data, site.getId());
|
||||
} catch (error) {
|
||||
if (CoreUtils.instance.isWebServiceError(error)) {
|
||||
// The WebService has thrown an error, this means that responses cannot be submitted.
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Couldn't connect to server, store in offline.
|
||||
return this.storeOffline(ws, data, component, componentId, site.getId());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the log online.
|
||||
*
|
||||
* @param ws WS name.
|
||||
* @param data Data to send to the WS.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved when log is successfully submitted. Rejected with object containing
|
||||
* the error message (if any) and a boolean indicating if the error was returned by WS.
|
||||
*/
|
||||
protected async logOnline<T extends CoreStatusWithWarningsWSResponse>(
|
||||
ws: string,
|
||||
data: Record<string, unknown>,
|
||||
siteId?: string,
|
||||
): Promise<void> {
|
||||
const site = await CoreSites.instance.getSite(siteId);
|
||||
|
||||
// Clone to have an unmodified data object.
|
||||
const wsData = Object.assign({}, data);
|
||||
|
||||
const response = await site.write<T>(ws, wsData);
|
||||
|
||||
if (!response.status) {
|
||||
// Return the warning. If no warnings (shouldn't happen), create a fake one.
|
||||
const warning = response.warnings?.[0] || {
|
||||
warningcode: 'errorlog',
|
||||
message: 'Error logging data.',
|
||||
};
|
||||
|
||||
throw new CoreWSError(warning);
|
||||
}
|
||||
|
||||
// Remove all the logs performed.
|
||||
// TODO: Remove this lines when time is accepted in logs.
|
||||
await this.deleteWSLogs(ws, data, siteId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform log online. Data will be saved offline for syncing.
|
||||
* It also triggers a Firebase view_item event.
|
||||
*
|
||||
* @param ws WS name.
|
||||
* @param data Data to send to the WS.
|
||||
* @param component Component name.
|
||||
* @param componentId Component ID.
|
||||
* @param name Name of the viewed item.
|
||||
* @param category Category of the viewed item.
|
||||
* @param eventData Data to pass to the Firebase event.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
logSingle(
|
||||
ws: string,
|
||||
data: Record<string, unknown>,
|
||||
component: string,
|
||||
componentId: number,
|
||||
name?: string,
|
||||
category?: string,
|
||||
eventData?: Record<string, unknown>,
|
||||
siteId?: string,
|
||||
): Promise<void> {
|
||||
CorePushNotifications.instance.logViewEvent(componentId, name, category, ws, eventData, siteId);
|
||||
|
||||
return this.log(ws, data, component, componentId, siteId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform log online. Data will be saved offline for syncing.
|
||||
* It also triggers a Firebase view_item_list event.
|
||||
*
|
||||
* @param ws WS name.
|
||||
* @param data Data to send to the WS.
|
||||
* @param component Component name.
|
||||
* @param componentId Component ID.
|
||||
* @param category Category of the viewed item.
|
||||
* @param eventData Data to pass to the Firebase event.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
logList(
|
||||
ws: string,
|
||||
data: Record<string, unknown>,
|
||||
component: string,
|
||||
componentId: number,
|
||||
category: string,
|
||||
eventData?: Record<string, unknown>,
|
||||
siteId?: string,
|
||||
): Promise<void> {
|
||||
CorePushNotifications.instance.logViewListEvent(category, ws, eventData, siteId);
|
||||
|
||||
return this.log(ws, data, component, componentId, siteId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save activity log for offline sync.
|
||||
*
|
||||
* @param ws WS name.
|
||||
* @param data Data to send to the WS.
|
||||
* @param component Component name.
|
||||
* @param componentId Component ID.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Resolved when done.
|
||||
*/
|
||||
protected async storeOffline(
|
||||
ws: string,
|
||||
data: Record<string, unknown>,
|
||||
component: string,
|
||||
componentId: number,
|
||||
siteId?: string,
|
||||
): Promise<void> {
|
||||
|
||||
const site = await CoreSites.instance.getSite(siteId);
|
||||
|
||||
const log: CoreCourseActivityLogDBRecord = {
|
||||
component,
|
||||
componentid: componentId,
|
||||
ws,
|
||||
data: CoreUtils.instance.sortAndStringify(data),
|
||||
time: CoreTimeUtils.instance.timestamp(),
|
||||
};
|
||||
|
||||
await site.getDb().insertRecord(ACTIVITY_LOG_TABLE, log);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync all the offline saved activity logs.
|
||||
*
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
async syncSite(siteId?: string): Promise<void> {
|
||||
const site = await CoreSites.instance.getSite(siteId);
|
||||
|
||||
siteId = site.getId();
|
||||
|
||||
const logs = await this.getAllLogs(siteId);
|
||||
|
||||
const unique: CoreCourseActivityLogDBRecord[] = [];
|
||||
|
||||
// TODO: When time is accepted on log, do not discard same logs.
|
||||
logs.forEach((log) => {
|
||||
// Just perform unique syncs.
|
||||
const found = unique.find((doneLog) => log.component == doneLog.component && log.componentid == doneLog.componentid &&
|
||||
log.ws == doneLog.ws && log.data == doneLog.data);
|
||||
|
||||
if (!found) {
|
||||
unique.push(log);
|
||||
}
|
||||
});
|
||||
|
||||
return this.syncLogs(unique, siteId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync the offline saved activity logs.
|
||||
*
|
||||
* @param component Component name.
|
||||
* @param componentId Component ID.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
async syncActivity(component: string, componentId: number, siteId?: string): Promise<void> {
|
||||
const site = await CoreSites.instance.getSite(siteId);
|
||||
|
||||
siteId = site.getId();
|
||||
|
||||
const logs = await this.getLogs(component, componentId, siteId);
|
||||
|
||||
const unique: CoreCourseActivityLogDBRecord[] = [];
|
||||
|
||||
// TODO: When time is accepted on log, do not discard same logs.
|
||||
logs.forEach((log) => {
|
||||
// Just perform unique syncs.
|
||||
const found = unique.find((doneLog) => log.ws == doneLog.ws && log.data == doneLog.data);
|
||||
|
||||
if (!found) {
|
||||
unique.push(log);
|
||||
}
|
||||
});
|
||||
|
||||
return this.syncLogs(unique, siteId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync and delete given logs.
|
||||
*
|
||||
* @param logs Array of log objects.
|
||||
* @param siteId Site Id.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected async syncLogs(logs: CoreCourseActivityLogDBRecord[], siteId: string): Promise<void> {
|
||||
await Promise.all(logs.map(async (log) => {
|
||||
const data = CoreTextUtils.instance.parseJSON<Record<string, unknown>>(log.data || '{}', {});
|
||||
|
||||
try {
|
||||
await this.logOnline(log.ws, data, siteId);
|
||||
} catch (error) {
|
||||
if (CoreUtils.instance.isWebServiceError(error)) {
|
||||
// The WebService has thrown an error, this means that responses cannot be submitted.
|
||||
await CoreUtils.instance.ignoreErrors(this.deleteWSLogs(log.ws, data, siteId));
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
await this.deleteWSLogsByComponent(log.component, log.componentid, log.ws, siteId);
|
||||
}));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class CoreCourseLogHelper extends makeSingleton(CoreCourseLogHelperProvider) {}
|
Loading…
Reference in New Issue