Merge pull request #1793 from albertgasset/MOBILE-2485

MOBILE-2485 data: Ratings
main
Juan Leyva 2019-03-06 16:35:05 +01:00 committed by GitHub
commit 62e392eab1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 137 additions and 12 deletions

View File

@ -22,7 +22,7 @@
<core-course-module-description [description]="description" [component]="component" [componentId]="componentId"></core-course-module-description>
<!-- Data done in offline but not synchronized -->
<div class="core-warning-card" icon-start *ngIf="hasOffline">
<div class="core-warning-card" icon-start *ngIf="hasOffline || hasOfflineRatings">
<ion-icon name="warning"></ion-icon>
{{ 'core.hasdatatosync' | translate: {$a: moduleName} }}
</div>

View File

@ -19,6 +19,9 @@ import { CoreUtilsProvider } from '@providers/utils/utils';
import { CoreGroupsProvider, CoreGroupInfo } from '@providers/groups';
import { CoreCourseModuleMainActivityComponent } from '@core/course/classes/main-activity-component';
import { CoreCommentsProvider } from '@core/comments/providers/comments';
import { CoreRatingProvider } from '@core/rating/providers/rating';
import { CoreRatingOfflineProvider } from '@core/rating/providers/offline';
import { CoreRatingSyncProvider } from '@core/rating/providers/sync';
import { AddonModDataProvider } from '../../providers/data';
import { AddonModDataHelperProvider } from '../../providers/helper';
import { AddonModDataOfflineProvider } from '../../providers/offline';
@ -74,11 +77,16 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp
protected hasComments = false;
protected fieldsArray: any;
hasOfflineRatings: boolean;
protected ratingOfflineObserver: any;
protected ratingSyncObserver: any;
constructor(injector: Injector, private dataProvider: AddonModDataProvider, private dataHelper: AddonModDataHelperProvider,
private dataOffline: AddonModDataOfflineProvider, @Optional() content: Content,
private dataSync: AddonModDataSyncProvider, private timeUtils: CoreTimeUtilsProvider,
private groupsProvider: CoreGroupsProvider, private commentsProvider: CoreCommentsProvider,
private modalCtrl: ModalController, private utils: CoreUtilsProvider, protected navCtrl: NavController) {
private modalCtrl: ModalController, private utils: CoreUtilsProvider, protected navCtrl: NavController,
private ratingOffline: CoreRatingOfflineProvider) {
super(injector, content);
// Refresh entries on change.
@ -89,7 +97,22 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp
return this.loadContent(true);
}
}, this.siteId);
// Listen for offline ratings saved and synced.
this.ratingOfflineObserver = this.eventsProvider.on(CoreRatingProvider.RATING_SAVED_EVENT, (data) => {
if (this.data && data.component == 'mod_data' && data.ratingArea == 'entry' && data.contextLevel == 'module'
&& data.instanceId == this.data.coursemodule) {
this.hasOfflineRatings = true;
}
});
this.ratingSyncObserver = this.eventsProvider.on(CoreRatingSyncProvider.SYNCED_EVENT, (data) => {
if (this.data && data.component == 'mod_data' && data.ratingArea == 'entry' && data.contextLevel == 'module'
&& data.instanceId == this.data.coursemodule) {
this.hasOfflineRatings = false;
}
});
}
/**
* Component being initialized.
*/
@ -487,6 +510,10 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp
}
});
}
}).then(() => {
return this.ratingOffline.hasRatings('mod_data', 'entry', 'module', this.data.coursemodule).then((hasRatings) => {
this.hasOfflineRatings = hasRatings;
});
});
}
@ -496,7 +523,17 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp
* @return {Promise<any>} Promise resolved when done.
*/
protected sync(): Promise<any> {
return this.dataSync.syncDatabase(this.data.id);
const promises = [
this.dataSync.syncDatabase(this.data.id),
this.dataSync.syncRatings(this.data.coursemodule)
];
return Promise.all(promises).then((results) => {
return results.reduce((a, b) => ({
updated: a.updated || b.updated,
warnings: (a.warnings || []).concat(b.warnings || []),
}), {updated: false});
});
}
/**
@ -515,5 +552,7 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp
ngOnDestroy(): void {
super.ngOnDestroy();
this.entryChangedObserver && this.entryChangedObserver.off();
this.ratingOfflineObserver && this.ratingOfflineObserver.off();
this.ratingSyncObserver && this.ratingSyncObserver.off();
}
}

View File

@ -30,6 +30,9 @@
<core-compile-html [text]="entryRendered" [jsData]="jsData" [extraImports]="extraImports"></core-compile-html>
</div>
<core-rating-rate *ngIf="data && ratingInfo" [ratingInfo]="ratingInfo" contextLevel="module" [instanceId]="data.coursemodule" [itemId]="entry.id" [itemSetId]="0" [courseId]="courseId" [aggregateMethod]="data.assessed" [scaleId]="data.scale" [userId]="entry.userid" (onUpdate)="ratingUpdated()"></core-rating-rate>
<core-rating-aggregate *ngIf="data && ratingInfo" [ratingInfo]="ratingInfo" contextLevel="module" [instanceId]="data.coursemodule" [itemId]="entry.id" [courseId]="courseId" [aggregateMethod]="data.assessed" [scaleId]="data.scale"></core-rating-aggregate>
<ion-item *ngIf="data && entry">
<core-comments contextLevel="module" [instanceId]="data.coursemodule" component="mod_data" [itemId]="entry.id" area="database_entry"></core-comments>
</ion-item>

View File

@ -19,6 +19,7 @@ import { CoreDirectivesModule } from '@directives/directives.module';
import { CoreComponentsModule } from '@components/components.module';
import { CoreCommentsComponentsModule } from '@core/comments/components/components.module';
import { CoreCompileHtmlComponentModule } from '@core/compile/components/compile-html/compile-html.module';
import { CoreRatingComponentsModule } from '@core/rating/components/components.module';
import { AddonModDataComponentsModule } from '../../components/components.module';
import { AddonModDataEntryPage } from './entry';
@ -33,7 +34,8 @@ import { AddonModDataEntryPage } from './entry';
CoreCompileHtmlComponentModule,
CoreCommentsComponentsModule,
IonicPageModule.forChild(AddonModDataEntryPage),
TranslateModule.forChild()
TranslateModule.forChild(),
CoreRatingComponentsModule
],
})
export class AddonModDataEntryPageModule {}

View File

@ -20,6 +20,7 @@ import { CoreSitesProvider } from '@providers/sites';
import { CoreGroupsProvider } from '@providers/groups';
import { CoreEventsProvider } from '@providers/events';
import { CoreCourseProvider } from '@core/course/providers/course';
import { CoreRatingInfo } from '@core/rating/providers/rating';
import { AddonModDataProvider } from '../../providers/data';
import { AddonModDataHelperProvider } from '../../providers/helper';
import { AddonModDataOfflineProvider } from '../../providers/offline';
@ -66,6 +67,7 @@ export class AddonModDataEntryPage implements OnDestroy {
cssClass = '';
extraImports = [AddonModDataComponentsModule];
jsData;
ratingInfo: CoreRatingInfo;
constructor(params: NavParams, protected utils: CoreUtilsProvider, protected groupsProvider: CoreGroupsProvider,
protected domUtils: CoreDomUtilsProvider, protected fieldsDelegate: AddonModDataFieldsDelegate,
@ -162,7 +164,9 @@ export class AddonModDataEntryPage implements OnDestroy {
return this.dataHelper.getEntry(this.data, this.entryId, this.offlineActions);
});
}).then((entry) => {
this.ratingInfo = entry.ratinginfo;
entry = entry.entry;
this.cssTemplate = this.dataHelper.prefixCSS(this.data.csstemplate, '.' + this.cssClass);
// Index contents by fieldid.
@ -311,6 +315,13 @@ export class AddonModDataEntryPage implements OnDestroy {
});
}
/**
* Function called when rating is updated online.
*/
ratingUpdated(): void {
this.dataProvider.invalidateEntryData(this.data.id, this.entryId);
}
/**
* Component being destroyed.
*/

View File

@ -652,10 +652,11 @@ export class AddonModDataProvider {
*
* @param {number} dataId Data ID for caching purposes.
* @param {number} entryId Entry ID.
* @param {boolean} [ignoreCache=false] True if it should ignore cached data (it'll always fail in offline or server down).
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when the database entry is retrieved.
*/
getEntry(dataId: number, entryId: number, siteId?: string): Promise<any> {
getEntry(dataId: number, entryId: number, ignoreCache: boolean = false, siteId?: string): Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => {
const params = {
entryid: entryId,
@ -665,6 +666,11 @@ export class AddonModDataProvider {
cacheKey: this.getEntryCacheKey(dataId, entryId)
};
if (ignoreCache) {
preSets['getFromCache'] = false;
preSets['emergencyCache'] = false;
}
return site.read('mod_data_get_entry', params, preSets);
});
}

View File

@ -354,7 +354,7 @@ export class AddonModDataHelperProvider {
getEntry(data: any, entryId: number, offlineActions?: any, siteId?: string): Promise<any> {
if (entryId > 0) {
// It's an online entry, get it from WS.
return this.dataProvider.getEntry(data.id, entryId, siteId);
return this.dataProvider.getEntry(data.id, entryId, false, siteId);
}
// It's an offline entry, search it in the offline actions.

View File

@ -24,6 +24,7 @@ import { CoreTimeUtilsProvider } from '@providers/utils/time';
import { CoreCommentsProvider } from '@core/comments/providers/comments';
import { CoreCourseProvider } from '@core/course/providers/course';
import { CoreCourseActivityPrefetchHandlerBase } from '@core/course/classes/activity-prefetch-handler';
import { CoreRatingProvider } from '@core/rating/providers/rating';
import { AddonModDataProvider } from './data';
import { AddonModDataHelperProvider } from './helper';
@ -41,7 +42,8 @@ export class AddonModDataPrefetchHandler extends CoreCourseActivityPrefetchHandl
courseProvider: CoreCourseProvider, filepoolProvider: CoreFilepoolProvider, sitesProvider: CoreSitesProvider,
domUtils: CoreDomUtilsProvider, protected dataProvider: AddonModDataProvider,
protected timeUtils: CoreTimeUtilsProvider, protected dataHelper: AddonModDataHelperProvider,
protected groupsProvider: CoreGroupsProvider, protected commentsProvider: CoreCommentsProvider) {
protected groupsProvider: CoreGroupsProvider, protected commentsProvider: CoreCommentsProvider,
private ratingProvider: CoreRatingProvider) {
super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils);
}
@ -282,7 +284,11 @@ export class AddonModDataPrefetchHandler extends CoreCourseActivityPrefetchHandl
});
info.entries.forEach((entry) => {
promises.push(this.dataProvider.getEntry(database.id, entry.id, siteId));
promises.push(this.dataProvider.getEntry(database.id, entry.id, true, siteId).then((entry) => {
return this.ratingProvider.prefetchRatings('module', module.id, database.scale, courseId, entry.ratinginfo,
siteId);
}));
if (database.comments) {
promises.push(this.commentsProvider.getComments('module', database.coursemodule, 'mod_data', entry.id,
'database_entry', 0, siteId));

View File

@ -28,6 +28,7 @@ import { TranslateService } from '@ngx-translate/core';
import { CoreCourseProvider } from '@core/course/providers/course';
import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper';
import { CoreSyncProvider } from '@providers/sync';
import { CoreRatingSyncProvider } from '@core/rating/providers/sync';
/**
* Service to sync databases.
@ -43,7 +44,8 @@ export class AddonModDataSyncProvider extends CoreSyncBaseProvider {
private eventsProvider: CoreEventsProvider, private dataProvider: AddonModDataProvider,
protected translate: TranslateService, private utils: CoreUtilsProvider, courseProvider: CoreCourseProvider,
syncProvider: CoreSyncProvider, protected textUtils: CoreTextUtilsProvider, timeUtils: CoreTimeUtilsProvider,
private dataHelper: AddonModDataHelperProvider, private logHelper: CoreCourseLogHelperProvider) {
private dataHelper: AddonModDataHelperProvider, private logHelper: CoreCourseLogHelperProvider,
private ratingSync: CoreRatingSyncProvider) {
super('AddonModDataSyncProvider', loggerProvider, sitesProvider, appProvider, syncProvider, textUtils, translate,
timeUtils);
@ -77,8 +79,12 @@ export class AddonModDataSyncProvider extends CoreSyncBaseProvider {
* @param {Promise<any>} Promise resolved if sync is successful, rejected if sync fails.
*/
protected syncAllDatabasesFunc(siteId?: string): Promise<any> {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
const promises = [];
// Get all data answers pending to be sent in the site.
return this.dataOffline.getAllEntries(siteId).then((offlineActions) => {
promises.push(this.dataOffline.getAllEntries(siteId).then((offlineActions) => {
const promises = {};
// Do not sync same database twice.
@ -101,7 +107,11 @@ export class AddonModDataSyncProvider extends CoreSyncBaseProvider {
// Promises will be an object so, convert to an array first;
return Promise.all(this.utils.objectToArray(promises));
});
}));
promises.push(this.syncRatings(undefined, siteId));
return Promise.all(promises);
}
/**
@ -232,7 +242,7 @@ export class AddonModDataSyncProvider extends CoreSyncBaseProvider {
entryId = entryActions[0].entryid;
if (entryId > 0) {
timePromise = this.dataProvider.getEntry(data.id, entryId, siteId).then((entry) => {
timePromise = this.dataProvider.getEntry(data.id, entryId, false, siteId).then((entry) => {
return entry.entry.timemodified;
}).catch(() => {
return -1;
@ -343,4 +353,52 @@ export class AddonModDataSyncProvider extends CoreSyncBaseProvider {
});
}
/**
* Synchronize offline ratings.
*
* @param {number} [cmId] Course module to be synced. If not defined, sync all databases.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved if sync is successful, rejected otherwise.
*/
syncRatings(cmId?: number, siteId?: string): Promise<any> {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
return this.ratingSync.syncRatings('mod_data', 'entry', 'module', cmId, 0, siteId).then((results) => {
let updated = false;
const warnings = [];
const promises = [];
results.forEach((result) => {
promises.push(this.dataProvider.getDatabase(result.itemSet.courseId, result.itemSet.instanceId, siteId)
.then((data) => {
const promises = [];
if (result.updated.length) {
updated = true;
// Invalidate entry of updated ratings.
result.updated.forEach((itemId) => {
promises.push(this.dataProvider.invalidateEntryData(data.id, itemId, siteId));
});
}
if (result.warnings.length) {
result.warnings.forEach((warning) => {
warnings.push(this.translate.instant('core.warningofflinedatadeleted', {
component: this.componentTranslate,
name: data.name,
error: warning
}));
});
}
return this.utils.allPromises(promises);
}));
});
return Promise.all(promises).then(() => {
return { updated, warnings };
});
});
}
}