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

View File

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

View File

@ -164,9 +164,9 @@ export class AddonCourseCompletionProvider {
* @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.
*/
async isPluginViewEnabledForCourse(courseId: number, preferCache: boolean = true): Promise<boolean> {
async isPluginViewEnabledForCourse(courseId?: number, preferCache: boolean = true): Promise<boolean> {
if (!courseId) {
throw new CoreError('No courseId provided');
return false;
}
const course = await CoreCourses.getUserCourse(courseId, preferCache);

View File

@ -13,11 +13,10 @@
// limitations under the License.
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 { CoreNavigator } from '@services/navigator';
import { makeSingleton } from '@singletons';
import { CoreEvents } from '@singletons/events';
import { AddonCourseCompletion } from '../coursecompletion';
/**
@ -29,20 +28,7 @@ export class AddonCourseCompletionUserHandlerService implements CoreUserProfileH
name = 'AddonCourseCompletion';
type = CoreUserDelegateService.TYPE_NEW_PAGE;
priority = 200;
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];
});
}
cacheEnabled = true;
/**
* @inheritdoc
@ -51,29 +37,18 @@ export class AddonCourseCompletionUserHandlerService implements CoreUserProfileH
return AddonCourseCompletion.isPluginViewEnabled();
}
/**
* @inheritdoc
*/
async isEnabledForCourse(courseId?: number): Promise<boolean> {
return AddonCourseCompletion.isPluginViewEnabledForCourse(courseId);
}
/**
* @inheritdoc
*/
async isEnabledForUser(user: CoreUserProfile, courseId?: number): Promise<boolean> {
if (!courseId) {
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;
return await AddonCourseCompletion.isPluginViewEnabledForUser(courseId!, user.id);
}
/**

View File

@ -40,6 +40,13 @@ export class AddonMessagesSendMessageUserHandlerService implements CoreUserProfi
return AddonMessages.isPluginEnabled();
}
/**
* @inheritdoc
*/
async isEnabledForCourse(): Promise<boolean> {
return !!CoreSites.getCurrentSite();
}
/**
* 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.
*/
async isEnabledForUser(user: CoreUserProfile): Promise<boolean> {
const currentSite = CoreSites.getCurrentSite();
if (!currentSite) {
return false;
}
const currentSite = CoreSites.getCurrentSite()!;
// 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.
* @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) {
return false;
}

View File

@ -34,73 +34,31 @@ export class CoreGradesUserHandlerService implements CoreUserProfileHandler {
name = 'CoreGrades:viewGrades';
priority = 400;
type = CoreUserDelegateService.TYPE_NEW_PAGE;
viewGradesEnabledCache = {};
cacheEnabled = true;
/**
* Clear view grades 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.
*/
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.
* @inheritdoc
*/
async isEnabled(): Promise<boolean> {
return true;
}
/**
* Check if handler is enabled for this user in this context.
*
* @param user User to check.
* @param courseId Course ID.
* @return Promise resolved with true if enabled, resolved with false otherwise.
* @inheritdoc
*/
async isEnabledForUser(user: CoreUserProfile, courseId: number): Promise<boolean> {
const cacheKey = this.getCacheKey(courseId, user.id);
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;
async isEnabledForCourse(courseId?: number): Promise<boolean> {
return CoreUtils.ignoreErrors(CoreGrades.isPluginEnabledForCourse(courseId), false);
}
/**
* Returns the data needed to render the handler.
*
* @return Data needed to render the handler.
* @inheritdoc
*/
async isEnabledForUser(user: CoreUserProfile, courseId?: number): Promise<boolean> {
return CoreUtils.promiseWorks(CoreGrades.getCourseGradesTable(courseId!, user.id));
}
/**
* @inheritdoc
*/
getDisplayData(): CoreUserProfileHandlerData {
return {

View File

@ -55,24 +55,12 @@ export class CoreSitePluginsUserProfileHandler extends CoreSitePluginsBaseHandle
/**
* @inheritdoc
*/
async isEnabledForUser(
user: CoreUserProfile,
async isEnabledForCourse(
courseId?: number,
): 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();
// Enabled for user, check if it's enabled for the course.
// Check if it's enabled for the course.
return CoreSitePlugins.isHandlerEnabledForCourse(
courseId,
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
*/

View File

@ -31,35 +31,23 @@ export class CoreUserProfileMailHandlerService implements CoreUserProfileHandler
type = CoreUserDelegateService.TYPE_COMMUNICATION;
/**
* Check if handler is enabled.
*
* @return Always enabled.
* @inheritdoc
*/
async isEnabled(): Promise<boolean> {
return true;
}
/**
* Check if handler is enabled for this user in this context.
*
* @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.
* @inheritdoc
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async isEnabledForUser(user: CoreUserProfile, courseId: number, navOptions?: unknown, admOptions?: unknown): Promise<boolean> {
async isEnabledForUser(user: CoreUserProfile): Promise<boolean> {
return user.id != CoreSites.getCurrentSiteUserId() && !!user.email;
}
/**
* Returns the data needed to render the handler.
*
* @return Data needed to render the handler.
* @inheritdoc
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
getDisplayData(user: CoreUserProfile, courseId: number): CoreUserProfileHandlerData {
getDisplayData(): CoreUserProfileHandlerData {
return {
icon: 'mail',
title: 'core.user.sendemail',

View File

@ -18,7 +18,7 @@ import { Subject, BehaviorSubject } from 'rxjs';
import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate';
import { CoreUtils } from '@services/utils/utils';
import { CoreEvents } from '@singletons/events';
import { CoreUserProfile } from './user';
import { CoreUserProfile, CoreUserProvider } from './user';
import { makeSingleton } from '@singletons';
import { CoreCourses, CoreCourseUserAdminOrNavOptionIndexed } from '@features/courses/services/courses';
import { CoreSites } from '@services/sites';
@ -42,21 +42,36 @@ export interface CoreUserProfileHandler extends CoreDelegateHandler {
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 navOptions Navigation options for the course.
* @param admOptions Admin options for the course.
* @return Whether or not the handler is enabled for a user.
*/
isEnabledForUser(
user: CoreUserProfile,
isEnabledForCourse?(
courseId?: number,
navOptions?: CoreCourseUserAdminOrNavOptionIndexed,
admOptions?: CoreCourseUserAdminOrNavOptionIndexed,
): 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.
*
@ -156,6 +171,11 @@ export class CoreUserDelegateService extends CoreDelegate<CoreUserProfileHandler
*/
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_';
// 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);
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];
try {
const enabled = await handler.isEnabledForUser(user, courseId, navOptions, admOptions);
const enabled = await this.getAndCacheEnabledForUserFromHandler(handler, user, courseId, navOptions, admOptions);
if (enabled) {
userData.handlers.push({
@ -288,6 +316,91 @@ export class CoreUserDelegateService extends CoreDelegate<CoreUserProfileHandler
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);