MOBILE-4526 filter: Use get_all_states WS if available

main
Dani Palou 2024-02-29 10:48:37 +01:00
parent 41e4292c48
commit 4fad121172
3 changed files with 278 additions and 14 deletions

View File

@ -15,7 +15,7 @@
import { Injectable, ViewContainerRef } from '@angular/core';
import { CoreSites } from '@services/sites';
import { CoreFilter, CoreFilterFilter, CoreFilterFormatTextOptions } from './filter';
import { CoreFilter, CoreFilterFilter, CoreFilterFormatTextOptions, CoreFilterStateValue } from './filter';
import { CoreFilterDefaultHandler } from './handlers/default-filter';
import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate';
import { CoreSite } from '@classes/sites/site';
@ -169,7 +169,7 @@ export class CoreFilterDelegateService extends CoreDelegate<CoreFilterHandler> {
filter: handler.filterName,
inheritedstate: 1,
instanceid: instanceId,
localstate: 1,
localstate: CoreFilterStateValue.ON,
});
}
@ -245,7 +245,10 @@ export class CoreFilterDelegateService extends CoreDelegate<CoreFilterHandler> {
skipFilters?: string[],
): boolean {
if (filter.localstate == -1 || (filter.localstate == 0 && filter.inheritedstate == -1)) {
if (
filter.localstate === CoreFilterStateValue.OFF ||
(filter.localstate === CoreFilterStateValue.INHERIT && filter.inheritedstate === CoreFilterStateValue.OFF)
) {
// Filter is disabled, ignore it.
return false;
}
@ -255,7 +258,7 @@ export class CoreFilterDelegateService extends CoreDelegate<CoreFilterHandler> {
return false;
}
if (skipFilters && skipFilters.indexOf(filter.filter) != -1) {
if (skipFilters && skipFilters.indexOf(filter.filter) !== -1) {
// Skip this filter.
return false;
}

View File

@ -23,6 +23,8 @@ import {
CoreFilterFormatTextOptions,
CoreFilterClassifiedFilters,
CoreFiltersGetAvailableInContextWSParamContext,
CoreFilterStateValue,
CoreFilterAllStates,
} from './filter';
import { CoreCourse } from '@features/course/services/course';
import { CoreCourses } from '@features/courses/services/courses';
@ -177,7 +179,7 @@ export class CoreFilterHelperProvider {
siteId?: string,
): Promise<CoreFilterFilter[]> {
// Check the right context to use.
const newContext = CoreFilter.convertContext(contextLevel, instanceId, { courseId: options.courseId });
const newContext = CoreFilter.getEffectiveContext(contextLevel, instanceId, { courseId: options.courseId });
contextLevel = newContext.contextLevel;
instanceId = newContext.instanceId;
@ -198,6 +200,11 @@ export class CoreFilterHelperProvider {
return await CoreFilterDelegate.getEnabledFilters(contextLevel, instanceId);
}
const filters = await this.getFiltersInContextUsingAllStates(contextLevel, instanceId, options, site);
if (filters) {
return filters;
}
const courseId = options.courseId;
let hasFilters = true;
@ -238,6 +245,95 @@ export class CoreFilterHelperProvider {
}
}
/**
* Get filters in context using the all states data.
*
* @param contextLevel The context level.
* @param instanceId Instance ID related to the context.
* @param options Options.
* @param site Site.
* @returns Filters, undefined if all states cannot be used.
*/
protected async getFiltersInContextUsingAllStates(
contextLevel: ContextLevel,
instanceId: number,
options: CoreFilterFormatTextOptions = {},
site?: CoreSite,
): Promise<CoreFilterFilter[] | undefined> {
site = site || CoreSites.getCurrentSite();
if (!CoreFilter.canGetAllStatesInSite(site)) {
return;
}
const allStates = await CoreFilter.getAllStates({ siteId: site?.getId() });
if (
contextLevel !== ContextLevel.SYSTEM &&
contextLevel !== ContextLevel.COURSECAT &&
this.hasCategoryOverride(allStates)
) {
// A category has an override, we cannot calculate the right filters for child contexts.
return;
}
const contexts = CoreFilter.getContextsTreeList(contextLevel, instanceId, { courseId: options.courseId });
const contextId = Object.values(allStates[contextLevel]?.[instanceId] ?? {})[0]?.contextid;
const filters: Record<string, CoreFilterFilter> = {};
contexts.reverse().forEach((context) => {
const isParentContext = context.contextLevel !== contextLevel;
const filtersInContext = allStates[context.contextLevel]?.[context.instanceId];
if (!filtersInContext) {
return;
}
for (const name in filtersInContext) {
const filterInContext = filtersInContext[name];
if (filterInContext.localstate === CoreFilterStateValue.DISABLED) {
// Ignore disabled filters to make it consistent with available in context.
continue;
}
filters[name] = {
contextlevel: contextLevel,
instanceid: instanceId,
contextid: contextId,
filter: name,
localstate: isParentContext ? CoreFilterStateValue.INHERIT : filterInContext.localstate,
inheritedstate: isParentContext ?
filterInContext.localstate :
filters[name]?.inheritedstate ?? filterInContext.localstate,
};
}
});
return Object.values(filters);
}
/**
* Check if there is an override for a category in the states of all filters.
*
* @param states States to check.
* @returns True if has category override, false otherwise.
*/
protected hasCategoryOverride(states: CoreFilterAllStates): boolean {
if (!states[ContextLevel.COURSECAT]) {
return false;
}
for (const instanceId in states[ContextLevel.COURSECAT]) {
for (const name in states[ContextLevel.COURSECAT][instanceId]) {
if (
states[ContextLevel.COURSECAT][instanceId][name].localstate !== states[ContextLevel.SYSTEM][0][name].localstate
) {
return true;
}
}
}
return false;
}
/**
* Get filters and format text.
*

View File

@ -15,7 +15,7 @@
import { Injectable } from '@angular/core';
import { CoreNetwork } from '@services/network';
import { CoreSites, CoreSitesReadingStrategy } from '@services/sites';
import { CoreSites, CoreSitesCommonWSOptions, CoreSitesReadingStrategy } from '@services/sites';
import { CoreSite } from '@classes/sites/site';
import { CoreWSExternalWarning } from '@services/ws';
import { CoreTextUtils } from '@services/utils/text';
@ -62,11 +62,37 @@ export class CoreFilterProvider {
});
}
/**
* Check if getting all states is available in site.
*
* @param siteId Site ID. If not defined, current site.
* @returns Whether it's available.
* @since 4.4
*/
async canGetAllStates(siteId?: string): Promise<boolean> {
const site = await CoreSites.getSite(siteId);
return this.canGetAllStatesInSite(site);
}
/**
* Check if getting all states is available in site.
*
* @param site Site. If not defined, current site.
* @returns Whether it's available.
* @since 4.4
*/
canGetAllStatesInSite(site?: CoreSite): boolean {
site = site || CoreSites.getCurrentSite();
return !!(site?.wsAvailable('core_filters_get_all_states'));
}
/**
* Returns whether or not we can get the available filters: the WS is available and the feature isn't disabled.
*
* @param siteId Site ID. If not defined, current site.
* @returns Promise resolved with boolean: whethe can get filters.
* @returns Whether can get filters.
*/
async canGetFilters(siteId?: string): Promise<boolean> {
const disabled = await this.checkFiltersDisabled(siteId);
@ -78,7 +104,7 @@ export class CoreFilterProvider {
* Returns whether or not we can get the available filters: the WS is available and the feature isn't disabled.
*
* @param site Site. If not defined, current site.
* @returns Promise resolved with boolean: whethe can get filters.
* @returns Whether can get filters.
*/
canGetFiltersInSite(site?: CoreSite): boolean {
return !this.checkFiltersDisabledInSite(site);
@ -153,7 +179,7 @@ export class CoreFilterProvider {
// Simulate the system context based on the inherited data.
filter.contextlevel = ContextLevel.SYSTEM;
filter.instanceid = 0;
filter.contextid = -1;
filter.contextid = undefined;
filter.localstate = filter.inheritedstate;
}
@ -171,7 +197,7 @@ export class CoreFilterProvider {
* @param options Options.
* @returns Context to use.
*/
convertContext(
getEffectiveContext(
contextLevel: ContextLevel,
instanceId: number,
options: {courseId?: number} = {},
@ -241,6 +267,53 @@ export class CoreFilterProvider {
return text;
}
/**
* Get cache key for get all states WS call.
*
* @returns Cache key.
*/
protected getAllStatesCacheKey(): string {
return this.ROOT_CACHE_KEY + 'allStates';
}
/**
* Get all the states for filters.
*
* @param options Options.
* @returns Promise resolved with the filters classified by context.
* @since 4.4
*/
async getAllStates(options: CoreSitesCommonWSOptions = {}): Promise<CoreFilterAllStates> {
const site = await CoreSites.getSite(options.siteId);
const preSets: CoreSiteWSPreSets = {
cacheKey: this.getAllStatesCacheKey(),
updateFrequency: CoreSite.FREQUENCY_RARELY,
// Use stale while revalidate by default, but always use the first value. If data is updated it will be stored in DB.
...CoreSites.getReadingStrategyPreSets(options.readingStrategy ?? CoreSitesReadingStrategy.STALE_WHILE_REVALIDATE),
};
const result = await site.read<CoreFilterGetAllStatesWSResponse>('core_filters_get_all_states', {}, preSets);
const classified: CoreFilterAllStates = {};
result.filters.forEach((filter) => {
classified[filter.contextlevel] = classified[filter.contextlevel] || {};
classified[filter.contextlevel][filter.instanceid] = classified[filter.contextlevel][filter.instanceid] || {};
classified[filter.contextlevel][filter.instanceid][filter.filter] = {
contextlevel: filter.contextlevel,
instanceid: filter.instanceid,
contextid: filter.contextid,
filter: filter.filter,
localstate: filter.state,
inheritedstate: filter.state,
};
});
return classified;
}
/**
* Get cache key for available in contexts WS calls.
*
@ -370,6 +443,45 @@ export class CoreFilterProvider {
}
}
/**
* Given a context, return the list of contexts used in the filters inheritance tree, from bottom to top.
* E.g. when using module, it will return the module context, course context (if course ID is supplied), category context
* (if categoy ID is supplied) and system context.
*
* @param contextLevel Context level.
* @param instanceId Instance ID.
* @param options Options
* @returns List of contexts.
*/
getContextsTreeList(
contextLevel: ContextLevel,
instanceId: number,
options: {courseId?: number; categoryId?: number} = {},
): { contextLevel: ContextLevel; instanceId: number }[] {
// Make sure context has been converted.
const newContext = CoreFilter.getEffectiveContext(contextLevel, instanceId, options);
contextLevel = newContext.contextLevel;
instanceId = newContext.instanceId;
const contexts = [
{ contextLevel, instanceId },
];
if (contextLevel === ContextLevel.MODULE && options.courseId) {
contexts.push({ contextLevel: ContextLevel.COURSE, instanceId: options.courseId });
}
if ((contextLevel === ContextLevel.MODULE || contextLevel === ContextLevel.COURSE) && options.categoryId) {
contexts.push({ contextLevel: ContextLevel.COURSECAT, instanceId: options.categoryId });
}
if (contextLevel !== ContextLevel.SYSTEM) {
contexts.push({ contextLevel: ContextLevel.SYSTEM, instanceId: 0 });
}
return contexts;
}
/**
* Invalidates all available in context WS calls.
*
@ -382,6 +494,18 @@ export class CoreFilterProvider {
await site.invalidateWsCacheForKeyStartingWith(this.getAvailableInContextsPrefixCacheKey());
}
/**
* Invalidates get all states WS call.
*
* @param siteId Site ID (empty for current site).
* @returns Promise resolved when the data is invalidated.
*/
async invalidateAllStates(siteId?: string): Promise<void> {
const site = await CoreSites.getSite(siteId);
await site.invalidateWsCacheForKey(this.getAllStatesCacheKey());
}
/**
* Invalidates available in context WS call.
*
@ -499,15 +623,15 @@ export type CoreFiltersGetAvailableInContextWSParamContext = {
};
/**
* Filter object returned by core_filters_get_available_in_context.
* Filter data.
*/
export type CoreFilterFilter = {
contextlevel: ContextLevel; // The context level where the filters are: (coursecat, course, module).
instanceid: number; // The instance id of item associated with the context.
contextid: number; // The context id.
contextid?: number; // The context id. It will be undefined in cases where it cannot be calculated in the app.
filter: string; // Filter plugin name.
localstate: number; // Filter state: 1 for on, -1 for off, 0 if inherit.
inheritedstate: number; // 1 or 0 to use when localstate is set to inherit.
localstate: CoreFilterStateValue; // Filter state.
inheritedstate: CoreFilterStateValue; // State to use when localstate is set to inherit.
};
/**
@ -518,6 +642,36 @@ export type CoreFilterGetAvailableInContextResult = {
warnings: CoreWSExternalWarning[]; // List of warnings.
};
/**
* Filter state returned by core_filters_get_all_states.
*/
export type CoreFilterState = {
contextlevel: ContextLevel; // The context level where the filters are: (coursecat, course, module).
instanceid: number; // The instance id of item associated with the context.
contextid: number; // The context id.
filter: string; // Filter plugin name.
state: CoreFilterStateValue; // Filter state.
sortorder: number; // Sort order.
};
/**
* Context levels enumeration.
*/
export const enum CoreFilterStateValue {
ON = 1,
INHERIT = 0,
OFF = -1,
DISABLED = -9999,
}
/**
* Result of core_filters_get_all_states.
*/
export type CoreFilterGetAllStatesWSResponse = {
filters: CoreFilterState[]; // Filter state.
warnings: CoreWSExternalWarning[]; // List of warnings.
};
/**
* Options that can be passed to format text.
*/
@ -541,3 +695,14 @@ export type CoreFilterClassifiedFilters = {
[instanceid: number]: CoreFilterFilter[];
};
};
/**
* All filter states classified by context, instance and filter name.
*/
export type CoreFilterAllStates = {
[contextlevel: string]: {
[instanceid: number]: {
[filtername: string]: CoreFilterFilter;
};
};
};