MOBILE-3629 user: Add cache to user handlers enabled for user

main
Pau Ferrer Ocaña 2021-03-10 15:34:39 +01:00
parent 29d0f006aa
commit 006ca78771
10 changed files with 175 additions and 147 deletions

View File

@ -14,7 +14,6 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { CoreCourseUserAdminOrNavOptionIndexed } from '@features/courses/services/courses'; import { CoreCourseUserAdminOrNavOptionIndexed } from '@features/courses/services/courses';
import { CoreUserProfile } from '@features/user/services/user';
import { CoreUserDelegateService, CoreUserProfileHandler, CoreUserProfileHandlerData } from '@features/user/services/user-delegate'; import { CoreUserDelegateService, CoreUserProfileHandler, CoreUserProfileHandlerData } from '@features/user/services/user-delegate';
import { CoreNavigator } from '@services/navigator'; import { CoreNavigator } from '@services/navigator';
import { makeSingleton } from '@singletons'; import { makeSingleton } from '@singletons';
@ -42,13 +41,11 @@ export class AddonBadgesUserHandlerService implements CoreUserProfileHandler {
/** /**
* Check if handler is enabled for this user in this context. * Check if handler is enabled for this user in this context.
* *
* @param user User to check.
* @param courseId Course ID. * @param courseId Course ID.
* @param navOptions Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions. * @param navOptions Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions.
* @return True if enabled, false otherwise. * @return True if enabled, false otherwise.
*/ */
async isEnabledForUser( async isEnabledForCourse(
user: CoreUserProfile,
courseId: number, courseId: number,
navOptions?: CoreCourseUserAdminOrNavOptionIndexed, navOptions?: CoreCourseUserAdminOrNavOptionIndexed,
): Promise<boolean> { ): Promise<boolean> {

View File

@ -35,13 +35,6 @@ export class AddonBlogUserHandlerService implements CoreUserProfileHandler {
return AddonBlog.isPluginEnabled(); return AddonBlog.isPluginEnabled();
} }
/**
* @inheritdoc
*/
async isEnabledForUser(): Promise<boolean> {
return true;
}
/** /**
* @inheritdoc * @inheritdoc
*/ */

View File

@ -164,9 +164,9 @@ export class AddonCourseCompletionProvider {
* @param preferCache True if shouldn't call WS if data is cached, false otherwise. * @param preferCache True if shouldn't call WS if data is cached, false otherwise.
* @return Promise resolved with true if plugin is enabled, rejected or resolved with false otherwise. * @return Promise resolved with true if plugin is enabled, rejected or resolved with false otherwise.
*/ */
async isPluginViewEnabledForCourse(courseId: number, preferCache: boolean = true): Promise<boolean> { async isPluginViewEnabledForCourse(courseId?: number, preferCache: boolean = true): Promise<boolean> {
if (!courseId) { if (!courseId) {
throw new CoreError('No courseId provided'); return false;
} }
const course = await CoreCourses.getUserCourse(courseId, preferCache); const course = await CoreCourses.getUserCourse(courseId, preferCache);

View File

@ -13,11 +13,10 @@
// limitations under the License. // limitations under the License.
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { CoreUserProfile, CoreUserProvider } from '@features/user/services/user'; import { CoreUserProfile } from '@features/user/services/user';
import { CoreUserProfileHandler, CoreUserDelegateService, CoreUserProfileHandlerData } from '@features/user/services/user-delegate'; import { CoreUserProfileHandler, CoreUserDelegateService, CoreUserProfileHandlerData } from '@features/user/services/user-delegate';
import { CoreNavigator } from '@services/navigator'; import { CoreNavigator } from '@services/navigator';
import { makeSingleton } from '@singletons'; import { makeSingleton } from '@singletons';
import { CoreEvents } from '@singletons/events';
import { AddonCourseCompletion } from '../coursecompletion'; import { AddonCourseCompletion } from '../coursecompletion';
/** /**
@ -29,20 +28,7 @@ export class AddonCourseCompletionUserHandlerService implements CoreUserProfileH
name = 'AddonCourseCompletion'; name = 'AddonCourseCompletion';
type = CoreUserDelegateService.TYPE_NEW_PAGE; type = CoreUserDelegateService.TYPE_NEW_PAGE;
priority = 200; priority = 200;
cacheEnabled = true;
protected enabledCache = {};
constructor() {
CoreEvents.on(CoreEvents.LOGOUT, () => {
this.enabledCache = {};
});
CoreEvents.on(CoreUserProvider.PROFILE_REFRESHED, (data) => {
const cacheKey = data.userId + '-' + data.courseId;
delete this.enabledCache[cacheKey];
});
}
/** /**
* @inheritdoc * @inheritdoc
@ -51,29 +37,18 @@ export class AddonCourseCompletionUserHandlerService implements CoreUserProfileH
return AddonCourseCompletion.isPluginViewEnabled(); return AddonCourseCompletion.isPluginViewEnabled();
} }
/**
* @inheritdoc
*/
async isEnabledForCourse(courseId?: number): Promise<boolean> {
return AddonCourseCompletion.isPluginViewEnabledForCourse(courseId);
}
/** /**
* @inheritdoc * @inheritdoc
*/ */
async isEnabledForUser(user: CoreUserProfile, courseId?: number): Promise<boolean> { async isEnabledForUser(user: CoreUserProfile, courseId?: number): Promise<boolean> {
if (!courseId) { return await AddonCourseCompletion.isPluginViewEnabledForUser(courseId!, user.id);
return false;
}
const courseEnabled = await AddonCourseCompletion.isPluginViewEnabledForCourse(courseId);
// If is not enabled in the course, is not enabled for the user.
if (!courseEnabled) {
return false;
}
const cacheKey = user.id + '-' + courseId;
if (typeof this.enabledCache[cacheKey] !== 'undefined') {
return this.enabledCache[cacheKey];
}
const enabled = await AddonCourseCompletion.isPluginViewEnabledForUser(courseId, user.id);
this.enabledCache[cacheKey] = enabled;
return enabled;
} }
/** /**

View File

@ -40,6 +40,13 @@ export class AddonMessagesSendMessageUserHandlerService implements CoreUserProfi
return AddonMessages.isPluginEnabled(); return AddonMessages.isPluginEnabled();
} }
/**
* @inheritdoc
*/
async isEnabledForCourse(): Promise<boolean> {
return !!CoreSites.getCurrentSite();
}
/** /**
* Check if handler is enabled for this user in this context. * Check if handler is enabled for this user in this context.
* *
@ -47,14 +54,10 @@ export class AddonMessagesSendMessageUserHandlerService implements CoreUserProfi
* @return Promise resolved with true if enabled, resolved with false otherwise. * @return Promise resolved with true if enabled, resolved with false otherwise.
*/ */
async isEnabledForUser(user: CoreUserProfile): Promise<boolean> { async isEnabledForUser(user: CoreUserProfile): Promise<boolean> {
const currentSite = CoreSites.getCurrentSite(); const currentSite = CoreSites.getCurrentSite()!;
if (!currentSite) {
return false;
}
// From 3.7 you can send messages to yourself. // From 3.7 you can send messages to yourself.
return user.id != currentSite.getUserId() || currentSite.isVersionGreaterEqualThan('3.7'); return user.id != CoreSites.getCurrentSiteUserId() || currentSite.isVersionGreaterEqualThan('3.7');
} }
/** /**

View File

@ -329,7 +329,7 @@ export class CoreGradesProvider {
* @param siteId Site ID. If not defined, current site. * @param siteId Site ID. If not defined, current site.
* @return Promise resolved with true if plugin is enabled, rejected or resolved with false otherwise. * @return Promise resolved with true if plugin is enabled, rejected or resolved with false otherwise.
*/ */
async isPluginEnabledForCourse(courseId: number, siteId?: string): Promise<boolean> { async isPluginEnabledForCourse(courseId?: number, siteId?: string): Promise<boolean> {
if (!courseId) { if (!courseId) {
return false; return false;
} }

View File

@ -34,73 +34,31 @@ export class CoreGradesUserHandlerService implements CoreUserProfileHandler {
name = 'CoreGrades:viewGrades'; name = 'CoreGrades:viewGrades';
priority = 400; priority = 400;
type = CoreUserDelegateService.TYPE_NEW_PAGE; type = CoreUserDelegateService.TYPE_NEW_PAGE;
viewGradesEnabledCache = {}; cacheEnabled = true;
/** /**
* Clear view grades cache. * @inheritdoc
* If a courseId and userId are specified, it will only delete the entry for that user and course.
*
* @param courseId Course ID.
* @param userId User ID.
*/
clearViewGradesCache(courseId?: number, userId?: number): void {
if (courseId && userId) {
delete this.viewGradesEnabledCache[this.getCacheKey(courseId, userId)];
} else {
this.viewGradesEnabledCache = {};
}
}
/**
* Get a cache key to identify a course and a user.
*
* @param courseId Course ID.
* @param userId User ID.
* @return Cache key.
*/
protected getCacheKey(courseId: number, userId: number): string {
return courseId + '#' + userId;
}
/**
* Check if handler is enabled.
*
* @return Always enabled.
*/ */
async isEnabled(): Promise<boolean> { async isEnabled(): Promise<boolean> {
return true; return true;
} }
/** /**
* Check if handler is enabled for this user in this context. * @inheritdoc
*
* @param user User to check.
* @param courseId Course ID.
* @return Promise resolved with true if enabled, resolved with false otherwise.
*/ */
async isEnabledForUser(user: CoreUserProfile, courseId: number): Promise<boolean> { async isEnabledForCourse(courseId?: number): Promise<boolean> {
const cacheKey = this.getCacheKey(courseId, user.id); return CoreUtils.ignoreErrors(CoreGrades.isPluginEnabledForCourse(courseId), false);
const cache = this.viewGradesEnabledCache[cacheKey];
if (typeof cache != 'undefined') {
return cache;
}
let enabled = await CoreUtils.ignoreErrors(CoreGrades.isPluginEnabledForCourse(courseId), false);
if (enabled) {
enabled = await CoreUtils.promiseWorks(CoreGrades.getCourseGradesTable(courseId, user.id));
}
this.viewGradesEnabledCache[cacheKey] = true;
return enabled;
} }
/** /**
* Returns the data needed to render the handler. * @inheritdoc
* */
* @return Data needed to render the handler. async isEnabledForUser(user: CoreUserProfile, courseId?: number): Promise<boolean> {
return CoreUtils.promiseWorks(CoreGrades.getCourseGradesTable(courseId!, user.id));
}
/**
* @inheritdoc
*/ */
getDisplayData(): CoreUserProfileHandlerData { getDisplayData(): CoreUserProfileHandlerData {
return { return {

View File

@ -55,24 +55,12 @@ export class CoreSitePluginsUserProfileHandler extends CoreSitePluginsBaseHandle
/** /**
* @inheritdoc * @inheritdoc
*/ */
async isEnabledForUser( async isEnabledForCourse(
user: CoreUserProfile,
courseId?: number, courseId?: number,
): Promise<boolean> { ): Promise<boolean> {
// First check if it's enabled for the user.
const enabledForUser = CoreSitePlugins.isHandlerEnabledForUser(
user.id,
this.handlerSchema.restricttocurrentuser,
this.initResult?.restrict,
);
if (!enabledForUser) {
return false;
}
courseId = courseId || CoreSites.getCurrentSiteHomeId(); courseId = courseId || CoreSites.getCurrentSiteHomeId();
// Enabled for user, check if it's enabled for the course. // Check if it's enabled for the course.
return CoreSitePlugins.isHandlerEnabledForCourse( return CoreSitePlugins.isHandlerEnabledForCourse(
courseId, courseId,
this.handlerSchema.restricttoenrolledcourses, this.handlerSchema.restricttoenrolledcourses,
@ -80,6 +68,19 @@ export class CoreSitePluginsUserProfileHandler extends CoreSitePluginsBaseHandle
); );
} }
/**
* @inheritdoc
*/
async isEnabledForUser(
user: CoreUserProfile,
): Promise<boolean> {
return CoreSitePlugins.isHandlerEnabledForUser(
user.id,
this.handlerSchema.restricttocurrentuser,
this.initResult?.restrict,
);
}
/** /**
* @inheritdoc * @inheritdoc
*/ */

View File

@ -31,35 +31,23 @@ export class CoreUserProfileMailHandlerService implements CoreUserProfileHandler
type = CoreUserDelegateService.TYPE_COMMUNICATION; type = CoreUserDelegateService.TYPE_COMMUNICATION;
/** /**
* Check if handler is enabled. * @inheritdoc
*
* @return Always enabled.
*/ */
async isEnabled(): Promise<boolean> { async isEnabled(): Promise<boolean> {
return true; return true;
} }
/** /**
* Check if handler is enabled for this user in this context. * @inheritdoc
*
* @param user User to check.
* @param courseId Course ID.
* @param navOptions Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions.
* @param admOptions Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions.
* @return Promise resolved with true if enabled, resolved with false otherwise.
*/ */
// eslint-disable-next-line @typescript-eslint/no-unused-vars async isEnabledForUser(user: CoreUserProfile): Promise<boolean> {
async isEnabledForUser(user: CoreUserProfile, courseId: number, navOptions?: unknown, admOptions?: unknown): Promise<boolean> {
return user.id != CoreSites.getCurrentSiteUserId() && !!user.email; return user.id != CoreSites.getCurrentSiteUserId() && !!user.email;
} }
/** /**
* Returns the data needed to render the handler. * @inheritdoc
*
* @return Data needed to render the handler.
*/ */
// eslint-disable-next-line @typescript-eslint/no-unused-vars getDisplayData(): CoreUserProfileHandlerData {
getDisplayData(user: CoreUserProfile, courseId: number): CoreUserProfileHandlerData {
return { return {
icon: 'mail', icon: 'mail',
title: 'core.user.sendemail', title: 'core.user.sendemail',

View File

@ -18,7 +18,7 @@ import { Subject, BehaviorSubject } from 'rxjs';
import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate'; import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate';
import { CoreUtils } from '@services/utils/utils'; import { CoreUtils } from '@services/utils/utils';
import { CoreEvents } from '@singletons/events'; import { CoreEvents } from '@singletons/events';
import { CoreUserProfile } from './user'; import { CoreUserProfile, CoreUserProvider } from './user';
import { makeSingleton } from '@singletons'; import { makeSingleton } from '@singletons';
import { CoreCourses, CoreCourseUserAdminOrNavOptionIndexed } from '@features/courses/services/courses'; import { CoreCourses, CoreCourseUserAdminOrNavOptionIndexed } from '@features/courses/services/courses';
import { CoreSites } from '@services/sites'; import { CoreSites } from '@services/sites';
@ -42,21 +42,36 @@ export interface CoreUserProfileHandler extends CoreDelegateHandler {
type: string; type: string;
/** /**
* Whether or not the handler is enabled for a user. * If isEnabledForUser Cache should be enabled.
*/
cacheEnabled?: boolean;
/**
* Whether or not the handler is enabled for a course.
* *
* @param user User object.
* @param courseId Course ID where to show. * @param courseId Course ID where to show.
* @param navOptions Navigation options for the course. * @param navOptions Navigation options for the course.
* @param admOptions Admin options for the course. * @param admOptions Admin options for the course.
* @return Whether or not the handler is enabled for a user. * @return Whether or not the handler is enabled for a user.
*/ */
isEnabledForUser( isEnabledForCourse?(
user: CoreUserProfile,
courseId?: number, courseId?: number,
navOptions?: CoreCourseUserAdminOrNavOptionIndexed, navOptions?: CoreCourseUserAdminOrNavOptionIndexed,
admOptions?: CoreCourseUserAdminOrNavOptionIndexed, admOptions?: CoreCourseUserAdminOrNavOptionIndexed,
): Promise<boolean>; ): Promise<boolean>;
/**
* Whether or not the handler is enabled for a user.
*
* @param user User object.
* @param courseId Course ID where to show.
* @return Whether or not the handler is enabled for a user.
*/
isEnabledForUser?(
user: CoreUserProfile,
courseId?: number,
): Promise<boolean>;
/** /**
* Returns the data needed to render the handler. * Returns the data needed to render the handler.
* *
@ -156,6 +171,11 @@ export class CoreUserDelegateService extends CoreDelegate<CoreUserProfileHandler
*/ */
static readonly UPDATE_HANDLER_EVENT = 'CoreUserDelegate_update_handler_event'; static readonly UPDATE_HANDLER_EVENT = 'CoreUserDelegate_update_handler_event';
/**
* Cache object that checks enabled for use.
*/
protected enabledForUserCache: Record<string, Record<string, boolean>> = {};
protected featurePrefix = 'CoreUserDelegate_'; protected featurePrefix = 'CoreUserDelegate_';
// Hold the handlers and the observable to notify them for each user. // Hold the handlers and the observable to notify them for each user.
@ -186,6 +206,14 @@ export class CoreUserDelegateService extends CoreDelegate<CoreUserProfileHandler
Object.assign(handler.data, data.data); Object.assign(handler.data, data.data);
this.userHandlers[data.userId].observable.next(this.userHandlers[data.userId].handlers); this.userHandlers[data.userId].observable.next(this.userHandlers[data.userId].handlers);
}); });
CoreEvents.on(CoreEvents.LOGOUT, () => {
this.clearHandlerCache();
});
CoreEvents.on(CoreUserProvider.PROFILE_REFRESHED, (data) => {
this.clearHandlerCache(data.courseId, data.userId);
});
} }
/** /**
@ -267,7 +295,7 @@ export class CoreUserDelegateService extends CoreDelegate<CoreUserProfileHandler
const handler = this.handlers[name]; const handler = this.handlers[name];
try { try {
const enabled = await handler.isEnabledForUser(user, courseId, navOptions, admOptions); const enabled = await this.getAndCacheEnabledForUserFromHandler(handler, user, courseId, navOptions, admOptions);
if (enabled) { if (enabled) {
userData.handlers.push({ userData.handlers.push({
@ -288,6 +316,91 @@ export class CoreUserDelegateService extends CoreDelegate<CoreUserProfileHandler
userData.observable.next(userData.handlers); userData.observable.next(userData.handlers);
} }
/**
* Helper funtion to get enabled for user from the handler.
*
* @param handler Handler object.
* @param user User object.
* @param courseId Course ID where to show.
* @param navOptions Navigation options for the course.
* @param admOptions Admin options for the course.
* @return Whether or not the handler is enabled for a user.
*/
protected async getAndCacheEnabledForUserFromHandler(
handler: CoreUserProfileHandler,
user: CoreUserProfile,
courseId?: number,
navOptions?: CoreCourseUserAdminOrNavOptionIndexed,
admOptions?: CoreCourseUserAdminOrNavOptionIndexed,
): Promise<boolean> {
if (handler.isEnabledForCourse) {
const enabledOnCourse = await handler.isEnabledForCourse(courseId, navOptions, admOptions);
if (!enabledOnCourse) {
// If is not enabled in the course, is not enabled for the user.
// Do not cache if this is false.
return false;
}
}
if (!handler.cacheEnabled) {
if (!handler.isEnabledForUser) {
// True by default.
return true;
}
return handler.isEnabledForUser(user, courseId);
}
if (typeof this.enabledForUserCache[handler.name] == 'undefined') {
this.enabledForUserCache[handler.name] = {};
}
const cacheKey = this.getCacheKey(courseId, user.id);
const cache = this.enabledForUserCache[handler.name][cacheKey];
if (typeof cache != 'undefined') {
return cache;
}
let enabled = true; // Default value.
if (handler.isEnabledForUser) {
enabled = await handler.isEnabledForUser(user, courseId);
}
this.enabledForUserCache[handler.name][cacheKey] = enabled;
return enabled;
}
/**
* Clear handler enabled for user cache.
* If a courseId and userId are specified, it will only delete the entry for that user and course.
*
* @param courseId Course ID.
* @param userId User ID.
*/
protected clearHandlerCache(courseId?: number, userId?: number): void {
if (courseId && userId) {
Object.keys(this.enabledHandlers).forEach((name) => {
delete this.enabledForUserCache[name][this.getCacheKey(courseId, userId)];
});
} else {
this.enabledForUserCache = {};
}
}
/**
* Get a cache key to identify a course and a user.
*
* @param courseId Course ID.
* @param userId User ID.
* @return Cache key.
*/
protected getCacheKey(courseId = 0, userId = 0): string {
return courseId + '#' + userId;
}
} }
export const CoreUserDelegate = makeSingleton(CoreUserDelegateService); export const CoreUserDelegate = makeSingleton(CoreUserDelegateService);