diff --git a/src/core/features/reportbuilder/classes/reports-source.ts b/src/core/features/reportbuilder/classes/reports-source.ts new file mode 100644 index 000000000..31449689f --- /dev/null +++ b/src/core/features/reportbuilder/classes/reports-source.ts @@ -0,0 +1,55 @@ +// (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 { CoreRoutedItemsManagerSource } from '@classes/items-management/routed-items-manager-source'; +import { CoreReportBuilder, CoreReportBuilderReport, REPORTS_LIST_LIMIT } from '../services/reportbuilder'; + +/** + * Provides a list of reports. + */ +export class CoreReportBuilderReportsSource extends CoreRoutedItemsManagerSource { + + /** + * @inheritdoc + */ + getItemPath(report: CoreReportBuilderReport): string { + return report.id.toString(); + } + + /** + * @inheritdoc + */ + protected async loadPageItems(page: number): Promise<{ items: CoreReportBuilderReport[]; hasMoreItems: boolean }> { + const reports = await CoreReportBuilder.getReports(page, this.getPageLength()); + + return { items: reports, hasMoreItems: reports.length > 0 }; + } + + /** + * @inheritdoc + */ + protected setItems(reports: CoreReportBuilderReport[], hasMoreItems: boolean): void { + const sortedReports = reports.slice(0); + reports.sort((a, b) => a.timecreated < b.timecreated ? 1 : -1); + super.setItems(sortedReports, hasMoreItems); + } + + /** + * @inheritdoc + */ + protected getPageLength(): number { + return REPORTS_LIST_LIMIT; + } + +} diff --git a/src/core/features/reportbuilder/services/handlers/reportbuilder.ts b/src/core/features/reportbuilder/services/handlers/reportbuilder.ts new file mode 100644 index 000000000..4a463f46e --- /dev/null +++ b/src/core/features/reportbuilder/services/handlers/reportbuilder.ts @@ -0,0 +1,59 @@ +// (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 { CoreUserDelegateService, CoreUserProfileHandler, CoreUserProfileHandlerData } from '@features/user/services/user-delegate'; +import { CoreNavigator } from '@services/navigator'; +import { makeSingleton } from '@singletons'; +import { CoreReportBuilder } from '../reportbuilder'; + +/** + * Handler to visualize custom reports. + */ +@Injectable({ providedIn: 'root' }) +export class CoreReportBuilderHandlerService implements CoreUserProfileHandler { + + static readonly PAGE_NAME = 'reportbuilder'; + + type = CoreUserDelegateService.TYPE_NEW_PAGE; + cacheEnabled = true; + name = 'CoreReportBuilderDelegate'; + priority = 350; + + /** + * @inheritdoc + */ + async isEnabled(): Promise { + return await CoreReportBuilder.isEnabled(); + } + + /** + * @inheritdoc + */ + getDisplayData(): CoreUserProfileHandlerData { + return { + class: 'core-report-builder', + icon: 'fa-list-alt', + title: 'core.reportbuilder.reportstab', + action: async (event): Promise => { + event.preventDefault(); + event.stopPropagation(); + await CoreNavigator.navigate(`/${CoreReportBuilderHandlerService.PAGE_NAME}`); + }, + }; + } + +} + +export const CoreReportBuilderHandler = makeSingleton(CoreReportBuilderHandlerService); diff --git a/src/core/features/reportbuilder/services/reportbuilder.ts b/src/core/features/reportbuilder/services/reportbuilder.ts new file mode 100644 index 000000000..2564e25cc --- /dev/null +++ b/src/core/features/reportbuilder/services/reportbuilder.ts @@ -0,0 +1,265 @@ +// (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. +// (C) Copyright 2015 Moodle Pty Ltd. +// + +import { Injectable } from '@angular/core'; +import { CoreError } from '@classes/errors/error'; +import { CoreSiteWSPreSets } from '@classes/site'; +import { CoreSites } from '@services/sites'; +import { CoreWSExternalWarning } from '@services/ws'; +import { makeSingleton } from '@singletons'; + +const ROOT_CACHE_KEY = 'mmaReportBuilder:'; +export const REPORTS_LIST_LIMIT = 20; +export const REPORT_ROWS_LIMIT = 20; + +@Injectable({ providedIn: 'root' }) +export class CoreReportBuilderService { + + /** + * Obtain the reports list. + * + * @param page Current page. + * @param perpage Reports obtained per page. + * @returns Reports list. + */ + async getReports(page?: number, perpage?: number): Promise { + const site = CoreSites.getRequiredCurrentSite(); + const preSets: CoreSiteWSPreSets = { cacheKey: this.getReportBuilderCacheKey() }; + const response = await site.read( + 'core_reportbuilder_list_reports', + { page, perpage }, + preSets, + ); + + return response.reports; + } + + /** + * Get the detail of a report. + * + * @param reportid Report id + * @param page Current page. + * @param perpage Rows obtained per page. + * @returns Detail of the report. + */ + async loadReport(reportid: number, page?: number, perpage?: number): Promise { + const site = CoreSites.getRequiredCurrentSite(); + const preSets: CoreSiteWSPreSets = { cacheKey: this.getReportBuilderReportCacheKey() }; + const report = await site.read( + 'core_reportbuilder_retrieve_report', + { reportid, page, perpage }, + preSets, + ); + + if (!report) { + throw new CoreError('An error ocurred.'); + } + + const settingsData: { + // eslint-disable-next-line @typescript-eslint/naming-convention + cardview_showfirsttitle: number; + // eslint-disable-next-line @typescript-eslint/naming-convention + cardview_visiblecolumns: number; + } = report.details.settingsdata ? JSON.parse(report.details.settingsdata) : {}; + + const mappedSettingsData: CoreReportBuilderReportDetailSettingsData = { + cardviewShowFirstTitle: settingsData.cardview_showfirsttitle === 1, + cardviewVisibleColumns: settingsData.cardview_visiblecolumns ?? 1, + }; + + return { + ...report, + details: { + ...report.details, + settingsdata: mappedSettingsData, + }, + data: { + ...report.data, + rows: [...report.data.rows.map(row => ({ columns: row.columns, isExpanded: row.isExpanded ?? false }))], + }, + }; + } + + /** + * View a report. + * + * @param reportid Report viewed. + * @returns Response of the WS. + */ + async viewReport(reportid: string): Promise { + const site = CoreSites.getRequiredCurrentSite(); + + await site.write('core_reportbuilder_view_report', { reportid }); + } + + /** + * Check if the feature is enabled or disabled. + * + * @returns Feature enabled or disabled. + */ + async isEnabled(): Promise { + const site = CoreSites.getRequiredCurrentSite(); + const hasTheVersionRequired = site.isVersionGreaterEqualThan('4.1'); + const hasAdvancedFeatureEnabled = site.canUseAdvancedFeature('enablecustomreports'); + const isFeatureDisabled = site.isFeatureDisabled('CoreReportBuilderDelegate'); + + return hasTheVersionRequired && hasAdvancedFeatureEnabled && !isFeatureDisabled; + } + + /** + * Invalidates reports list WS calls. + * + * @returns Promise resolved when the list is invalidated. + */ + async invalidateReportsList(): Promise { + const site = CoreSites.getRequiredCurrentSite(); + await site.invalidateWsCacheForKey(this.getReportBuilderCacheKey()); + } + + /** + * Invalidates report WS calls. + * + * @returns Promise resolved when report is invalidated. + */ + async invalidateReport(): Promise { + const site = CoreSites.getCurrentSite(); + + if (!site) { + return; + } + + await site.invalidateWsCacheForKey(this.getReportBuilderReportCacheKey()); + } + + /** + * Get cache key for report builder list WS calls. + * + * @returns Cache key. + */ + protected getReportBuilderCacheKey(): string { + return ROOT_CACHE_KEY + 'list'; + } + + /** + * Get cache key for report builder report WS calls. + * + * @returns Cache key. + */ + protected getReportBuilderReportCacheKey(): string { + return ROOT_CACHE_KEY + 'report'; + } + +} + +export const CoreReportBuilder = makeSingleton(CoreReportBuilderService); + +type CoreReportBuilderPagination = { + page?: number; + perpage?: number; +}; + +export type CoreReportBuilderRetrieveReportWSParams = CoreReportBuilderPagination & { + reportid: number; // Report ID. +}; + +/** + * Data returned by core_reportbuilder_list_reports WS. + */ +export type CoreReportBuilderListReportsWSResponse = { + reports: CoreReportBuilderReportWSResponse[]; + warnings?: CoreWSExternalWarning[]; +}; + +export type CoreReportBuilderReportWSResponse = { + name: string; // Name. + source: string; // Source. + type: number; // Type. + uniquerows: boolean; // Uniquerows. + conditiondata: string; // Conditiondata. + settingsdata: string | null; // Settingsdata. + contextid: number; // Contextid. + component: string; // Component. + area: string; // Area. + itemid: number; // Itemid. + usercreated: number; // Usercreated. + id: number; // Id. + timecreated: number; // Timecreated. + timemodified: number; // Timemodified. + usermodified: number; // Usermodified. + sourcename: string; // Sourcename. + modifiedby: { + id: number; // Id. + email: string; // Email. + idnumber: string; // Idnumber. + phone1: string; // Phone1. + phone2: string; // Phone2. + department: string; // Department. + institution: string; // Institution. + fullname: string; // Fullname. + identity: string; // Identity. + profileurl: string; // Profileurl. + profileimageurl: string; // Profileimageurl. + profileimageurlsmall: string; // Profileimageurlsmall. + }; +}; + +/** + * Data returned by core_reportbuilder_retrieve_report WS. + */ +export type CoreReportBuilderRetrieveReportWSResponse = { + details: CoreReportBuilderReportWSResponse; + data: CoreReportBuilderReportDataWSResponse; + warnings?: CoreWSExternalWarning[]; +}; + +export interface CoreReportBuilderRetrieveReportMapped extends Omit { + details: CoreReportBuilderReportDetail; +} + +export type CoreReportBuilderReportDataWSResponse = { + headers: string[]; // Headers. + rows: { // Rows. + columns: string[]; // Columns. + isExpanded: boolean; + }[]; + totalrowcount: number; // Totalrowcount. +}; + +/** + * Params of core_reportbuilder_view_report WS. + */ +export type CoreReportBuilderViewReportWSParams = { + reportid: number; // Report ID. +}; + +/** + * Data returned by core_reportbuilder_view_report WS. + */ +export type CoreReportBuilderViewReportWSResponse = { + status: boolean; // Success. + warnings?: CoreWSExternalWarning[]; +}; + +export interface CoreReportBuilderReportDetail extends Omit { + settingsdata: CoreReportBuilderReportDetailSettingsData; +} + +export type CoreReportBuilderReportDetailSettingsData = { + cardviewShowFirstTitle: boolean; + cardviewVisibleColumns: number; +}; + +export interface CoreReportBuilderReport extends CoreReportBuilderReportWSResponse {};