Merge pull request #3533 from alfonso-salces/MOBILE-4077
Mobile 4077 - Support user custom reportsmain
commit
c1370250f5
|
@ -1652,6 +1652,12 @@
|
|||
"core.courses.totalcoursesearchresults": "local_moodlemobileapp",
|
||||
"core.currentdevice": "local_moodlemobileapp",
|
||||
"core.custom": "form",
|
||||
"core.reportbuilder.modifiedby": "tool_reportbuilder",
|
||||
"core.reportbuilder.reportstab": "tool_reportbuilder",
|
||||
"core.reportbuilder.reportsource": "tool_reportbuilder",
|
||||
"core.reportbuilder.timecreated": "tool_reportbuilder",
|
||||
"core.reportbuilder.showcolumns": "local_moodlemobileapp",
|
||||
"core.reportbuilder.hidecolumns": "local_moodlemobileapp",
|
||||
"core.datastoredoffline": "local_moodlemobileapp",
|
||||
"core.date": "moodle",
|
||||
"core.datecreated": "repository",
|
||||
|
|
|
@ -43,6 +43,7 @@ import { CoreUserModule } from './user/user.module';
|
|||
import { CoreUserToursModule } from './usertours/user-tours.module';
|
||||
import { CoreViewerModule } from './viewer/viewer.module';
|
||||
import { CoreXAPIModule } from './xapi/xapi.module';
|
||||
import { CoreReportBuilderModule } from './reportbuilder/reportbuilder.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
|
@ -74,6 +75,7 @@ import { CoreXAPIModule } from './xapi/xapi.module';
|
|||
CoreUserToursModule,
|
||||
CoreViewerModule,
|
||||
CoreXAPIModule,
|
||||
CoreReportBuilderModule,
|
||||
|
||||
// Import last to allow overrides.
|
||||
CoreEmulatorModule,
|
||||
|
|
|
@ -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<CoreReportBuilderReport> {
|
||||
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
// (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 { NgModule } from '@angular/core';
|
||||
import { CoreSharedModule } from '@/core/shared.module';
|
||||
import { CoreReportBuilderReportColumnComponent } from './report-column/report-column';
|
||||
import { CoreReportBuilderReportDetailComponent } from './report-detail/report-detail';
|
||||
import { CoreReportBuilderReportSummaryComponent } from './report-summary/report-summary';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CoreSharedModule,
|
||||
],
|
||||
declarations: [
|
||||
CoreReportBuilderReportDetailComponent,
|
||||
CoreReportBuilderReportColumnComponent,
|
||||
CoreReportBuilderReportSummaryComponent,
|
||||
],
|
||||
exports: [
|
||||
CoreReportBuilderReportDetailComponent,
|
||||
CoreReportBuilderReportColumnComponent,
|
||||
CoreReportBuilderReportSummaryComponent,
|
||||
],
|
||||
})
|
||||
export class CoreReportBuilderComponentsModule {}
|
|
@ -0,0 +1,11 @@
|
|||
<ion-item class="ion-text-wrap" lines="inset" [detail]="false" [button]="isExpandable" [attr.aria-expanded]="isExpanded"
|
||||
[attr.aria-controls]="'core-report-builder-column-' + rowIndex"
|
||||
[attr.aria-label]="(isExpanded ? 'core.hidecolumns' : 'core.showcolumns') | translate" (click)="toggleRow()">
|
||||
<ion-label>
|
||||
<h3 *ngIf="columnIndex !== 0 || (columnIndex === 0 && showFirstTitle)"> {{ header }} </h3>
|
||||
<core-format-text [text]="column" contextLevel="site" [contextInstanceId]="contextId"></core-format-text>
|
||||
</ion-label>
|
||||
<ion-icon [class.expandable-status-icon-expanded]="!isExpanded" slot="end" aria-hidden="true" name="fas-chevron-up"
|
||||
class="expandable-status-icon" *ngIf="isExpandable" flip-rtl>
|
||||
</ion-icon>
|
||||
</ion-item>
|
|
@ -0,0 +1,11 @@
|
|||
@import "~theme/globals";
|
||||
|
||||
:host {
|
||||
--rotate-expandable: rotate(180deg);
|
||||
|
||||
.expandable-status-icon {
|
||||
font-size: var(--text-size);
|
||||
@include margin-horizontal(0, 2px);
|
||||
@include core-transition(transform, 200ms);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
// (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 { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'core-report-builder-report-column',
|
||||
templateUrl: './report-column.html',
|
||||
styleUrls: ['./report-column.scss'],
|
||||
})
|
||||
export class CoreReportBuilderReportColumnComponent {
|
||||
|
||||
@Input() isExpanded = false;
|
||||
@Input() isExpandable = false;
|
||||
@Input() showFirstTitle = false;
|
||||
@Input() columnIndex!: number;
|
||||
@Input() rowIndex!: number;
|
||||
@Input() column!: string;
|
||||
@Input() contextId!: number;
|
||||
@Input() header!: string;
|
||||
@Output() onToggleRow: EventEmitter<number> = new EventEmitter();
|
||||
|
||||
/**
|
||||
* Emits row click
|
||||
*/
|
||||
toggleRow(): void {
|
||||
this.onToggleRow.emit(this.rowIndex);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
<ng-container *ngIf="state$ | async as state">
|
||||
|
||||
<core-loading [hideUntil]="state.loaded">
|
||||
|
||||
<ng-container *ngIf="state.report?.data?.rows && state.report?.data?.headers && state.report?.details; else empty">
|
||||
|
||||
<ion-refresher slot="fixed" [disabled]="!state.loaded" (ionRefresh)="refreshReport($event.target)">
|
||||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||
</ion-refresher>
|
||||
|
||||
<ng-container *ngIf="isCardLayout">
|
||||
<ion-card *ngFor="let row of state.report.data.rows; let rowIndex = index">
|
||||
<ion-list class="ion-text-wrap">
|
||||
<core-report-builder-report-column *ngFor="let column of row.columns | slice:0:row.isExpanded ?
|
||||
row.columns.length : state.cardVisibleColumns; let columnIndex = index" [columnIndex]="columnIndex"
|
||||
[rowIndex]="rowIndex" [isExpandable]="columnIndex === 0 && row.columns.length > state.cardVisibleColumns"
|
||||
[isExpanded]="row.isExpanded" [showFirstTitle]="state.cardviewShowFirstTitle"
|
||||
[contextId]="state.report.details.contextid" [header]="state.report.data.headers[columnIndex]" [column]="column"
|
||||
(onToggleRow)="toggleRow(rowIndex)">
|
||||
</core-report-builder-report-column>
|
||||
</ion-list>
|
||||
</ion-card>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="!isCardLayout">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th *ngFor="let header of state.report.data.headers">
|
||||
{{ header }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let row of state.report.data.rows">
|
||||
<td *ngFor="let column of row.columns">
|
||||
<core-format-text [text]="column" [contextLevel]="'site'"
|
||||
[contextInstanceId]="state.report.details.contextid">
|
||||
</core-format-text>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</ng-container>
|
||||
|
||||
</ng-container>
|
||||
|
||||
<ng-template #empty>
|
||||
<core-empty-box *ngIf="!state.report?.data?.rows || !state.report?.data?.headers || !state.report?.details" icon="fa-list-alt"
|
||||
[message]="'core.course.nocontentavailable' | translate"></core-empty-box>
|
||||
</ng-template>
|
||||
|
||||
<core-infinite-loading *ngIf="!isBlock && state.report?.data?.rows && state.report?.data?.headers && state.report?.details"
|
||||
[enabled]="state.canLoadMoreRows" (action)="fetchMoreInfo($event)" [error]="state.errorLoadingRows">
|
||||
</core-infinite-loading>
|
||||
|
||||
|
||||
</core-loading>
|
||||
|
||||
</ng-container>
|
|
@ -0,0 +1,44 @@
|
|||
@import "~theme/globals";
|
||||
|
||||
:host {
|
||||
--header-background: var(--white);
|
||||
--border-color: var(--stroke);
|
||||
|
||||
.report-title {
|
||||
ion-item {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
table {
|
||||
width: 98%;
|
||||
margin: 1em auto;
|
||||
border-collapse: collapse;
|
||||
color: var(--ion-text-color);
|
||||
overflow-x: auto;
|
||||
display: block;
|
||||
|
||||
tbody {
|
||||
display: table;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: var(--header-background);
|
||||
}
|
||||
|
||||
tr {
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
|
||||
&:nth-child(even) {
|
||||
background: var(--light);
|
||||
}
|
||||
}
|
||||
|
||||
th, td {
|
||||
@include padding(8px, 8px, 8px, null);
|
||||
text-align: start;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,201 @@
|
|||
// (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 { ChangeDetectionStrategy, Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
||||
import { CoreError } from '@classes/errors/error';
|
||||
import {
|
||||
CoreReportBuilder,
|
||||
CoreReportBuilderReportDetail,
|
||||
CoreReportBuilderRetrieveReportMapped,
|
||||
REPORT_ROWS_LIMIT,
|
||||
} from '@features/reportbuilder/services/reportbuilder';
|
||||
import { IonRefresher } from '@ionic/angular';
|
||||
import { CoreNavigator } from '@services/navigator';
|
||||
import { CoreScreen } from '@services/screen';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
|
||||
@Component({
|
||||
selector: 'core-report-builder-report-detail',
|
||||
templateUrl: './report-detail.html',
|
||||
styleUrls: ['./report-detail.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class CoreReportBuilderReportDetailComponent implements OnInit {
|
||||
|
||||
@Input() reportId!: string;
|
||||
@Input() isBlock = true;
|
||||
@Input() perPage?: number;
|
||||
@Input() layout: 'card' | 'table' | 'adaptative' = 'adaptative';
|
||||
@Output() onReportLoaded = new EventEmitter<CoreReportBuilderReportDetail>();
|
||||
|
||||
get isCardLayout(): boolean {
|
||||
return this.layout === 'card' || (CoreScreen.isMobile && this.layout === 'adaptative');
|
||||
}
|
||||
|
||||
state$: Readonly<BehaviorSubject<CoreReportBuilderReportDetailState>> = new BehaviorSubject({
|
||||
report: null,
|
||||
loaded: false,
|
||||
canLoadMoreRows: true,
|
||||
errorLoadingRows: false,
|
||||
cardviewShowFirstTitle: false,
|
||||
cardVisibleColumns: 1,
|
||||
page: 0,
|
||||
});
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async ngOnInit(): Promise<void> {
|
||||
await this.getReport();
|
||||
this.updateState({ loaded: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* Get report data.
|
||||
*/
|
||||
async getReport(): Promise<void> {
|
||||
if (!this.reportId) {
|
||||
CoreDomUtils.showErrorModal(new CoreError('No report found'));
|
||||
CoreNavigator.back();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const { page } = this.state$.getValue();
|
||||
|
||||
const report = await CoreReportBuilder.loadReport(parseInt(this.reportId), page,this.perPage ?? REPORT_ROWS_LIMIT);
|
||||
|
||||
if (!report) {
|
||||
CoreDomUtils.showErrorModal(new CoreError('No report found'));
|
||||
CoreNavigator.back();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await CoreReportBuilder.viewReport(this.reportId);
|
||||
|
||||
this.updateState({
|
||||
report,
|
||||
cardVisibleColumns: report.details.settingsdata.cardviewVisibleColumns,
|
||||
cardviewShowFirstTitle: report.details.settingsdata.cardviewShowFirstTitle,
|
||||
});
|
||||
|
||||
this.onReportLoaded.emit(report.details);
|
||||
}
|
||||
|
||||
updateState(state: Partial<CoreReportBuilderReportDetailState>): void {
|
||||
const previousState = this.state$.getValue();
|
||||
this.state$.next({ ...previousState, ...state });
|
||||
}
|
||||
|
||||
/**
|
||||
* Update report data.
|
||||
*
|
||||
* @param ionRefresher ionic refresher.
|
||||
*/
|
||||
async refreshReport(ionRefresher?: IonRefresher): Promise<void> {
|
||||
await CoreUtils.ignoreErrors(CoreReportBuilder.invalidateReport());
|
||||
this.updateState({ page: 0, canLoadMoreRows: false });
|
||||
await CoreUtils.ignoreErrors(this.getReport());
|
||||
await ionRefresher?.complete();
|
||||
this.updateState({ canLoadMoreRows: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment page of report rows.
|
||||
*/
|
||||
protected incrementPage(): void {
|
||||
const { page } = this.state$.getValue();
|
||||
this.updateState({ page: page + 1 });
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a new batch of pages.
|
||||
*
|
||||
* @param complete Completion callback.
|
||||
*/
|
||||
async fetchMoreInfo(complete: () => void): Promise<void> {
|
||||
const { canLoadMoreRows, report } = this.state$.getValue();
|
||||
|
||||
if (!canLoadMoreRows) {
|
||||
complete();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.incrementPage();
|
||||
|
||||
const { page: currentPage } = this.state$.getValue();
|
||||
|
||||
const newReport = await CoreReportBuilder.loadReport(parseInt(this.reportId), currentPage, REPORT_ROWS_LIMIT);
|
||||
|
||||
if (!report || !newReport || newReport.data.rows.length === 0) {
|
||||
this.updateState({ canLoadMoreRows: false });
|
||||
complete();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.updateState({
|
||||
report: {
|
||||
...report,
|
||||
data: {
|
||||
...report.data,
|
||||
rows: [
|
||||
...report.data.rows,
|
||||
...newReport.data.rows,
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
CoreDomUtils.showErrorModalDefault(error, 'Error loading more reports');
|
||||
|
||||
this.updateState({ canLoadMoreRows: false });
|
||||
this.updateState({ errorLoadingRows: true });
|
||||
}
|
||||
|
||||
complete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand or close card.
|
||||
*
|
||||
* @param rowIndex card to expand or close.
|
||||
*/
|
||||
toggleRow(rowIndex: number): void {
|
||||
const { report } = this.state$.getValue();
|
||||
|
||||
if (!report?.data?.rows[rowIndex]) {
|
||||
return;
|
||||
}
|
||||
|
||||
report.data.rows[rowIndex].isExpanded = !report.data.rows[rowIndex].isExpanded;
|
||||
this.updateState({ report });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export type CoreReportBuilderReportDetailState = {
|
||||
report: CoreReportBuilderRetrieveReportMapped | null;
|
||||
loaded: boolean;
|
||||
canLoadMoreRows: boolean;
|
||||
errorLoadingRows: boolean;
|
||||
cardviewShowFirstTitle: boolean;
|
||||
cardVisibleColumns: number;
|
||||
page: number;
|
||||
};
|
|
@ -0,0 +1,39 @@
|
|||
<ion-header class="no-title">
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start">
|
||||
<ion-back-button [text]="'core.back' | translate"></ion-back-button>
|
||||
</ion-buttons>
|
||||
<ion-title>
|
||||
</ion-title>
|
||||
<ion-buttons slot="end">
|
||||
<ion-button fill="clear" (click)="closeModal()" [attr.aria-label]="'core.close' | translate">
|
||||
<ion-icon slot="icon-only" name="fas-times" aria-hidden="true"></ion-icon>
|
||||
</ion-button>
|
||||
</ion-buttons>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content>
|
||||
<div class="list-item-limited-width">
|
||||
<ion-item class="ion-text-wrap course-name">
|
||||
<ion-label>
|
||||
<h1>
|
||||
<core-format-text [text]="reportDetail.name" contextLevel="report" [contextInstanceId]="reportDetail.id">
|
||||
</core-format-text>
|
||||
</h1>
|
||||
</ion-label>
|
||||
<ion-button fill="clear" [href]="reportUrl" core-link [showBrowserWarning]="false"
|
||||
[attr.aria-label]="'core.openinbrowser' | translate" slot="end">
|
||||
<ion-icon name="fas-external-link-alt" slot="icon-only" aria-hidden="true"></ion-icon>
|
||||
</ion-button>
|
||||
</ion-item>
|
||||
|
||||
<ion-item class="ion-text-wrap" [detail]="false" *ngFor="let item of reportDetailToDisplay">
|
||||
<ion-label>
|
||||
<p>{{ item.title | translate }}</p>
|
||||
<core-format-text [text]="item.text" contextLevel="report" [contextInstanceId]="reportDetail.id">
|
||||
</core-format-text>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</div>
|
||||
</ion-content>
|
|
@ -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 { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
|
||||
import { CoreReportBuilderReportDetail } from '@features/reportbuilder/services/reportbuilder';
|
||||
import { CoreFormatDatePipe } from '@pipes/format-date';
|
||||
import { CoreSites } from '@services/sites';
|
||||
import { ModalController } from '@singletons';
|
||||
|
||||
@Component({
|
||||
selector: 'core-report-builder-report-summary',
|
||||
templateUrl: './report-summary.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class CoreReportBuilderReportSummaryComponent implements OnInit {
|
||||
|
||||
@Input() reportDetail!: CoreReportBuilderReportDetail;
|
||||
reportUrl!: string;
|
||||
reportDetailToDisplay!: { title: string; text: string }[];
|
||||
|
||||
ngOnInit(): void {
|
||||
const formatDate = new CoreFormatDatePipe();
|
||||
const site = CoreSites.getRequiredCurrentSite();
|
||||
this.reportUrl = `${site.getURL()}/reportbuilder/view.php?id=${this.reportDetail.id}`;
|
||||
this.reportDetailToDisplay = [
|
||||
{
|
||||
title: 'core.reportbuilder.reportsource',
|
||||
text: this.reportDetail.sourcename,
|
||||
},
|
||||
{
|
||||
title: 'core.reportbuilder.timecreated',
|
||||
text: formatDate.transform(this.reportDetail.timecreated * 1000),
|
||||
},
|
||||
{
|
||||
title: 'addon.mod_data.timemodified',
|
||||
text: formatDate.transform(this.reportDetail.timemodified * 1000),
|
||||
},
|
||||
{
|
||||
title: 'core.reportbuilder.modifiedby',
|
||||
text: this.reportDetail.modifiedby.fullname,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
closeModal(): void {
|
||||
ModalController.dismiss();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"modifiedby": "Modified by",
|
||||
"reportstab": "Reports",
|
||||
"reportsource": "Report source",
|
||||
"timecreated": "Time created",
|
||||
"showcolumns": "Show columns",
|
||||
"hidecolumns": "Hide columns"
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start">
|
||||
<ion-back-button [text]="'core.back' | translate"></ion-back-button>
|
||||
</ion-buttons>
|
||||
<ion-title>
|
||||
<h1>{{ 'core.reportbuilder.reportstab' | translate }}</h1>
|
||||
</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content *ngIf="state$ | async as state">
|
||||
<ion-refresher slot="fixed" [disabled]="!state.loaded" (ionRefresh)="refreshReports($event.target)">
|
||||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||
</ion-refresher>
|
||||
<core-loading [hideUntil]="state.loaded">
|
||||
<ion-list *ngIf="reports.items?.length; else empty">
|
||||
<ion-item [attr.aria-current]="reports.getItemAriaCurrent(report)" [detail]="true" class="ion-text-wrap" [button]="true"
|
||||
*ngFor="let report of reports.items" (click)="reports.select(report)">
|
||||
<ion-label>
|
||||
<h3>{{ report.name }}</h3>
|
||||
<p>{{ report.sourcename }}</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
|
||||
<ng-template #empty>
|
||||
<core-empty-box *ngIf="!reports.items?.length" icon="fa-list-alt" [message]="'core.course.nocontentavailable' | translate">
|
||||
</core-empty-box>
|
||||
</ng-template>
|
||||
|
||||
<core-infinite-loading *ngIf="reports.items?.length" [enabled]="reports.loaded && !reports.completed"
|
||||
(action)="fetchMoreReports($event)" [error]="state.loadMoreError">
|
||||
</core-infinite-loading>
|
||||
</core-loading>
|
||||
</ion-content>
|
|
@ -0,0 +1,128 @@
|
|||
// (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 { AfterViewInit, ChangeDetectionStrategy, Component, OnDestroy } from '@angular/core';
|
||||
import { CoreListItemsManager } from '@classes/items-management/list-items-manager';
|
||||
import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker';
|
||||
import { CoreReportBuilderReportsSource } from '@features/reportbuilder/classes/reports-source';
|
||||
import { CoreReportBuilder, CoreReportBuilderReport, REPORTS_LIST_LIMIT } from '@features/reportbuilder/services/reportbuilder';
|
||||
import { IonRefresher } from '@ionic/angular';
|
||||
import { CoreNavigator } from '@services/navigator';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
|
||||
@Component({
|
||||
selector: 'core-report-builder-list',
|
||||
templateUrl: './list.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class CoreReportBuilderListPage implements AfterViewInit, OnDestroy {
|
||||
|
||||
reports!: CoreListItemsManager<CoreReportBuilderReport, CoreReportBuilderReportsSource>;
|
||||
|
||||
state$: Readonly<BehaviorSubject<CoreReportBuilderListState>> = new BehaviorSubject({
|
||||
page: 1,
|
||||
perpage: REPORTS_LIST_LIMIT,
|
||||
loaded: false,
|
||||
loadMoreError: false,
|
||||
});
|
||||
|
||||
constructor() {
|
||||
try {
|
||||
const source = CoreRoutedItemsManagerSourcesTracker.getOrCreateSource(CoreReportBuilderReportsSource, []);
|
||||
this.reports = new CoreListItemsManager(source, CoreReportBuilderListPage);
|
||||
} catch (error) {
|
||||
CoreDomUtils.showErrorModal(error);
|
||||
CoreNavigator.back();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async ngAfterViewInit(): Promise<void> {
|
||||
try {
|
||||
await this.fetchReports(true);
|
||||
this.updateState({ loaded: true });
|
||||
} catch (error) {
|
||||
CoreDomUtils.showErrorModalDefault(error, 'Error loading reports');
|
||||
|
||||
this.reports.reset();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update reports list or loads it.
|
||||
*
|
||||
* @param reload is reoading or not.
|
||||
*/
|
||||
async fetchReports(reload: boolean): Promise<void> {
|
||||
reload ? await this.reports.reload() : await this.reports.load();
|
||||
this.updateState({ loadMoreError: false });
|
||||
}
|
||||
|
||||
/**
|
||||
* Properties of the state to update.
|
||||
*
|
||||
* @param state Object to update.
|
||||
*/
|
||||
updateState(state: Partial<CoreReportBuilderListState>): void {
|
||||
const previousState = this.state$.getValue();
|
||||
this.state$.next({ ...previousState, ...state });
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a new batch of Reports.
|
||||
*
|
||||
* @param complete Completion callback.
|
||||
*/
|
||||
async fetchMoreReports(complete: () => void): Promise<void> {
|
||||
try {
|
||||
await this.fetchReports(false);
|
||||
} catch (error) {
|
||||
CoreDomUtils.showErrorModalDefault(error, 'Error loading more reports');
|
||||
|
||||
this.updateState({ loadMoreError: true });
|
||||
}
|
||||
|
||||
complete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh reports list.
|
||||
*
|
||||
* @param ionRefresher ionRefresher.
|
||||
*/
|
||||
async refreshReports(ionRefresher?: IonRefresher): Promise<void> {
|
||||
await CoreUtils.ignoreErrors(CoreReportBuilder.invalidateReportsList());
|
||||
await CoreUtils.ignoreErrors(this.fetchReports(true));
|
||||
await ionRefresher?.complete();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.reports.destroy();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
type CoreReportBuilderListState = {
|
||||
page: number;
|
||||
perpage: number;
|
||||
loaded: boolean;
|
||||
loadMoreError: boolean;
|
||||
};
|
|
@ -0,0 +1,21 @@
|
|||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start">
|
||||
<ion-back-button [text]="'core.back' | translate"></ion-back-button>
|
||||
</ion-buttons>
|
||||
<ion-buttons slot="end">
|
||||
<ion-button fill="clear" (click)="openInfo()" [attr.aria-label]="'core.close' | translate">
|
||||
<ion-icon slot="icon-only" name="fas-info-circle" aria-hidden="true"></ion-icon>
|
||||
</ion-button>
|
||||
</ion-buttons>
|
||||
<ion-title *ngIf="reportDetail">
|
||||
<h1> {{ reportDetail.name }} </h1>
|
||||
<p class="subheading"> {{ reportDetail.sourcename }} </p>
|
||||
</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content>
|
||||
<core-report-builder-report-detail [isBlock]="false" [reportId]="reportId" (onReportLoaded)="loadReportDetail($event)">
|
||||
</core-report-builder-report-detail>
|
||||
</ion-content>
|
|
@ -0,0 +1,52 @@
|
|||
// (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 { Component, OnInit } from '@angular/core';
|
||||
import { CoreReportBuilderReportSummaryComponent } from '@features/reportbuilder/components/report-summary/report-summary';
|
||||
import { CoreReportBuilderReportDetail } from '@features/reportbuilder/services/reportbuilder';
|
||||
import { CoreNavigator } from '@services/navigator';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
|
||||
@Component({
|
||||
selector: 'core-report-builder-report',
|
||||
templateUrl: './report.html',
|
||||
})
|
||||
export class CoreReportBuilderReportPage implements OnInit {
|
||||
|
||||
reportId!: string;
|
||||
reportDetail?: CoreReportBuilderReportDetail;
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.reportId = CoreNavigator.getRequiredRouteParam('id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the report detail
|
||||
*
|
||||
* @param reportDetail it contents the detail of the report.
|
||||
*/
|
||||
loadReportDetail(reportDetail: CoreReportBuilderReportDetail): void {
|
||||
this.reportDetail = reportDetail;
|
||||
}
|
||||
|
||||
openInfo(): void {
|
||||
CoreDomUtils.openSideModal<void>({
|
||||
component: CoreReportBuilderReportSummaryComponent,
|
||||
componentProps: { reportDetail: this.reportDetail },
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
// (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 { CoreSharedModule } from '@/core/shared.module';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { CoreReportBuilderComponentsModule } from './components/components.module';
|
||||
import { CoreReportBuilderListPage } from './pages/list/list';
|
||||
import { CoreReportBuilderReportPage } from './pages/report/report';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: CoreReportBuilderListPage,
|
||||
},
|
||||
{
|
||||
path: ':id',
|
||||
component: CoreReportBuilderReportPage,
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CoreSharedModule,
|
||||
CoreReportBuilderComponentsModule,
|
||||
RouterModule.forChild(routes),
|
||||
],
|
||||
declarations: [
|
||||
CoreReportBuilderListPage,
|
||||
CoreReportBuilderReportPage,
|
||||
],
|
||||
})
|
||||
export class CoreReportBuilderLazyModule {}
|
|
@ -0,0 +1,39 @@
|
|||
// (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 { APP_INITIALIZER, NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { CoreUserDelegate } from '@features/user/services/user-delegate';
|
||||
import { CoreReportBuilderHandler, CoreReportBuilderHandlerService } from './services/handlers/reportbuilder';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: CoreReportBuilderHandlerService.PAGE_NAME,
|
||||
loadChildren: () => import('./reportbuilder-lazy.module').then(m => m.CoreReportBuilderLazyModule),
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
providers: [
|
||||
{
|
||||
provide: APP_INITIALIZER,
|
||||
multi: true,
|
||||
useValue: () => {
|
||||
CoreUserDelegate.registerHandler(CoreReportBuilderHandler.instance);
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
export class CoreReportBuilderModule {}
|
|
@ -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<boolean> {
|
||||
return await CoreReportBuilder.isEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
getDisplayData(): CoreUserProfileHandlerData {
|
||||
return {
|
||||
class: 'core-report-builder',
|
||||
icon: 'fa-list-alt',
|
||||
title: 'core.reportbuilder.reportstab',
|
||||
action: async (event): Promise<void> => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
await CoreNavigator.navigate(`/${CoreReportBuilderHandlerService.PAGE_NAME}`);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const CoreReportBuilderHandler = makeSingleton(CoreReportBuilderHandlerService);
|
|
@ -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<CoreReportBuilderReport[]> {
|
||||
const site = CoreSites.getRequiredCurrentSite();
|
||||
const preSets: CoreSiteWSPreSets = { cacheKey: this.getReportBuilderCacheKey() };
|
||||
const response = await site.read<CoreReportBuilderListReportsWSResponse>(
|
||||
'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<CoreReportBuilderRetrieveReportMapped> {
|
||||
const site = CoreSites.getRequiredCurrentSite();
|
||||
const preSets: CoreSiteWSPreSets = { cacheKey: this.getReportBuilderReportCacheKey() };
|
||||
const report = await site.read<CoreReportBuilderRetrieveReportWSResponse>(
|
||||
'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<void> {
|
||||
const site = CoreSites.getRequiredCurrentSite();
|
||||
|
||||
await site.write<CoreReportBuilderViewReportWSResponse>('core_reportbuilder_view_report', { reportid });
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the feature is enabled or disabled.
|
||||
*
|
||||
* @returns Feature enabled or disabled.
|
||||
*/
|
||||
async isEnabled(): Promise<boolean> {
|
||||
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<void> {
|
||||
const site = CoreSites.getRequiredCurrentSite();
|
||||
await site.invalidateWsCacheForKey(this.getReportBuilderCacheKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates report WS calls.
|
||||
*
|
||||
* @returns Promise resolved when report is invalidated.
|
||||
*/
|
||||
async invalidateReport(): Promise<void> {
|
||||
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<CoreReportBuilderRetrieveReportWSResponse, 'details'> {
|
||||
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<CoreReportBuilderReportWSResponse, 'settingsdata'> {
|
||||
settingsdata: CoreReportBuilderReportDetailSettingsData;
|
||||
}
|
||||
|
||||
export type CoreReportBuilderReportDetailSettingsData = {
|
||||
cardviewShowFirstTitle: boolean;
|
||||
cardviewVisibleColumns: number;
|
||||
};
|
||||
|
||||
export interface CoreReportBuilderReport extends CoreReportBuilderReportWSResponse {};
|
|
@ -0,0 +1,145 @@
|
|||
@app @javascript @core_reportbuilder
|
||||
Feature: Report builder
|
||||
|
||||
Background:
|
||||
Given the following "core_reportbuilder > Reports" exist:
|
||||
| name | source | default |
|
||||
| My report 01 | core_user\reportbuilder\datasource\users | 1 |
|
||||
| My report 02 | core_user\reportbuilder\datasource\users | 2 |
|
||||
| My report 03 | core_user\reportbuilder\datasource\users | 3 |
|
||||
| My report 04 | core_user\reportbuilder\datasource\users | 4 |
|
||||
| My report 05 | core_user\reportbuilder\datasource\users | 5 |
|
||||
| My report 06 | core_user\reportbuilder\datasource\users | 6 |
|
||||
| My report 07 | core_user\reportbuilder\datasource\users | 7 |
|
||||
| My report 08 | core_user\reportbuilder\datasource\users | 8 |
|
||||
| My report 09 | core_user\reportbuilder\datasource\users | 9 |
|
||||
| My report 10 | core_user\reportbuilder\datasource\users | 10 |
|
||||
| My report 11 | core_user\reportbuilder\datasource\users | 11 |
|
||||
| My report 12 | core_user\reportbuilder\datasource\users | 12 |
|
||||
| My report 13 | core_user\reportbuilder\datasource\users | 13 |
|
||||
| My report 14 | core_user\reportbuilder\datasource\users | 14 |
|
||||
| My report 15 | core_user\reportbuilder\datasource\users | 15 |
|
||||
| My report 16 | core_user\reportbuilder\datasource\users | 16 |
|
||||
| My report 17 | core_user\reportbuilder\datasource\users | 17 |
|
||||
| My report 18 | core_user\reportbuilder\datasource\users | 18 |
|
||||
| My report 19 | core_user\reportbuilder\datasource\users | 19 |
|
||||
| My report 20 | core_user\reportbuilder\datasource\users | 20 |
|
||||
| My report 21 | core_user\reportbuilder\datasource\users | 21 |
|
||||
| My report 22 | core_user\reportbuilder\datasource\users | 22 |
|
||||
| My report 23 | core_user\reportbuilder\datasource\users | 23 |
|
||||
| My report 24 | core_user\reportbuilder\datasource\users | 24 |
|
||||
| My report 25 | core_user\reportbuilder\datasource\users | 25 |
|
||||
| My report 26 | core_user\reportbuilder\datasource\users | 26 |
|
||||
| My report 27 | core_user\reportbuilder\datasource\users | 27 |
|
||||
| My report 28 | core_user\reportbuilder\datasource\users | 28 |
|
||||
| My report 29 | core_user\reportbuilder\datasource\users | 29 |
|
||||
| My report 30 | core_user\reportbuilder\datasource\users | 30 |
|
||||
| My report 31 | core_user\reportbuilder\datasource\users | 31 |
|
||||
| My report 32 | core_user\reportbuilder\datasource\users | 32 |
|
||||
| My report 33 | core_user\reportbuilder\datasource\users | 33 |
|
||||
| My report 34 | core_user\reportbuilder\datasource\users | 34 |
|
||||
| My report 35 | core_user\reportbuilder\datasource\users | 35 |
|
||||
And the following "core_reportbuilder > Columns" exist:
|
||||
| report | uniqueidentifier |
|
||||
| My report 01 | user:fullname |
|
||||
| My report 02 | user:fullname |
|
||||
| My report 03 | user:fullname |
|
||||
| My report 04 | user:fullname |
|
||||
| My report 05 | user:fullname |
|
||||
| My report 06 | user:fullname |
|
||||
| My report 07 | user:fullname |
|
||||
| My report 08 | user:fullname |
|
||||
| My report 09 | user:fullname |
|
||||
| My report 10 | user:fullname |
|
||||
| My report 11 | user:fullname |
|
||||
| My report 12 | user:fullname |
|
||||
| My report 13 | user:fullname |
|
||||
| My report 14 | user:fullname |
|
||||
| My report 15 | user:fullname |
|
||||
| My report 16 | user:fullname |
|
||||
| My report 17 | user:fullname |
|
||||
| My report 18 | user:fullname |
|
||||
| My report 19 | user:fullname |
|
||||
| My report 20 | user:fullname |
|
||||
| My report 21 | user:fullname |
|
||||
| My report 22 | user:fullname |
|
||||
| My report 23 | user:fullname |
|
||||
| My report 24 | user:fullname |
|
||||
| My report 25 | user:fullname |
|
||||
| My report 26 | user:fullname |
|
||||
| My report 27 | user:fullname |
|
||||
| My report 28 | user:fullname |
|
||||
| My report 29 | user:fullname |
|
||||
| My report 30 | user:fullname |
|
||||
| My report 31 | user:fullname |
|
||||
| My report 32 | user:fullname |
|
||||
| My report 33 | user:fullname |
|
||||
| My report 34 | user:fullname |
|
||||
| My report 35 | user:fullname |
|
||||
And the following "core_reportbuilder > Audiences" exist:
|
||||
| report | configdata | classname |
|
||||
| My report 01 | | core_reportbuilder\reportbuilder\audience\allusers |
|
||||
| My report 02 | | core_reportbuilder\reportbuilder\audience\allusers |
|
||||
| My report 03 | | core_reportbuilder\reportbuilder\audience\allusers |
|
||||
| My report 04 | | core_reportbuilder\reportbuilder\audience\allusers |
|
||||
| My report 05 | | core_reportbuilder\reportbuilder\audience\allusers |
|
||||
| My report 06 | | core_reportbuilder\reportbuilder\audience\allusers |
|
||||
| My report 07 | | core_reportbuilder\reportbuilder\audience\allusers |
|
||||
| My report 08 | | core_reportbuilder\reportbuilder\audience\allusers |
|
||||
| My report 09 | | core_reportbuilder\reportbuilder\audience\allusers |
|
||||
| My report 10 | | core_reportbuilder\reportbuilder\audience\allusers |
|
||||
| My report 11 | | core_reportbuilder\reportbuilder\audience\allusers |
|
||||
| My report 12 | | core_reportbuilder\reportbuilder\audience\allusers |
|
||||
| My report 13 | | core_reportbuilder\reportbuilder\audience\allusers |
|
||||
| My report 14 | | core_reportbuilder\reportbuilder\audience\allusers |
|
||||
| My report 15 | | core_reportbuilder\reportbuilder\audience\allusers |
|
||||
| My report 16 | | core_reportbuilder\reportbuilder\audience\allusers |
|
||||
| My report 17 | | core_reportbuilder\reportbuilder\audience\allusers |
|
||||
| My report 18 | | core_reportbuilder\reportbuilder\audience\allusers |
|
||||
| My report 19 | | core_reportbuilder\reportbuilder\audience\allusers |
|
||||
| My report 20 | | core_reportbuilder\reportbuilder\audience\allusers |
|
||||
| My report 21 | | core_reportbuilder\reportbuilder\audience\allusers |
|
||||
| My report 22 | | core_reportbuilder\reportbuilder\audience\allusers |
|
||||
| My report 23 | | core_reportbuilder\reportbuilder\audience\allusers |
|
||||
| My report 24 | | core_reportbuilder\reportbuilder\audience\allusers |
|
||||
| My report 25 | | core_reportbuilder\reportbuilder\audience\allusers |
|
||||
| My report 26 | | core_reportbuilder\reportbuilder\audience\allusers |
|
||||
| My report 27 | | core_reportbuilder\reportbuilder\audience\allusers |
|
||||
| My report 28 | | core_reportbuilder\reportbuilder\audience\allusers |
|
||||
| My report 29 | | core_reportbuilder\reportbuilder\audience\allusers |
|
||||
| My report 30 | | core_reportbuilder\reportbuilder\audience\allusers |
|
||||
| My report 31 | | core_reportbuilder\reportbuilder\audience\allusers |
|
||||
| My report 32 | | core_reportbuilder\reportbuilder\audience\allusers |
|
||||
| My report 33 | | core_reportbuilder\reportbuilder\audience\allusers |
|
||||
| My report 34 | | core_reportbuilder\reportbuilder\audience\allusers |
|
||||
| My report 35 | | core_reportbuilder\reportbuilder\audience\allusers |
|
||||
And the following "users" exist:
|
||||
| username | firstname | lastname | email | city |
|
||||
| student1 | Lionel | Smith | lionel@example.com | Bilbao |
|
||||
|
||||
Scenario: Open report in mobile
|
||||
Given I enter the app
|
||||
And I log in as "student1"
|
||||
And I press the user menu button in the app
|
||||
When I press "Reports" in the app
|
||||
|
||||
# Find report in the screen
|
||||
Then I should find "My report 03" in the app
|
||||
And I press "My report 03" in the app
|
||||
And I should find "My report 03" in the app
|
||||
And I should find "Lionel Smith" in the app
|
||||
But I should not find "My report 02" in the app
|
||||
|
||||
Scenario: Open report in tablet
|
||||
Given I enter the app
|
||||
And I change viewport size to "1200x640"
|
||||
And I log in as "student1"
|
||||
And I press the user menu button in the app
|
||||
When I press "Reports" in the app
|
||||
|
||||
# Find report in the screen
|
||||
Then I should find "My report 02" in the app
|
||||
And I press "My report 02" in the app
|
||||
And I should find "My report 02" in the app
|
||||
And I should find "Lionel Smith" in the app
|
||||
But I should not find "My report 03" in the app
|
Loading…
Reference in New Issue