commit
c3bbcd2702
|
@ -408,6 +408,7 @@
|
|||
"addon.mod_assign.submitassignment_help": "assign",
|
||||
"addon.mod_assign.submittedearly": "assign",
|
||||
"addon.mod_assign.submittedlate": "assign",
|
||||
"addon.mod_assign.syncblockedusercomponent": "local_moodlemobileapp",
|
||||
"addon.mod_assign.timemodified": "assign",
|
||||
"addon.mod_assign.timeremaining": "assign",
|
||||
"addon.mod_assign.ungroupedusers": "assign",
|
||||
|
|
|
@ -299,7 +299,7 @@ export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityCo
|
|||
*/
|
||||
protected hasSyncSucceed(result: any): boolean {
|
||||
if (result.updated) {
|
||||
this.submissionComponent && this.submissionComponent.invalidateAndRefresh();
|
||||
this.submissionComponent && this.submissionComponent.invalidateAndRefresh(false);
|
||||
}
|
||||
|
||||
return result.updated;
|
||||
|
@ -324,7 +324,7 @@ export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityCo
|
|||
}
|
||||
|
||||
return Promise.all(promises).finally(() => {
|
||||
this.submissionComponent && this.submissionComponent.invalidateAndRefresh();
|
||||
this.submissionComponent && this.submissionComponent.invalidateAndRefresh(true);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
</ion-item>
|
||||
|
||||
<!-- Tabs: see the submission or grade it. -->
|
||||
<core-tabs [selectedIndex]="selectedTab" [hideUntil]="loaded" parentScrollable="true">
|
||||
<core-tabs [selectedIndex]="selectedTab" [hideUntil]="loaded" parentScrollable="true" (ionChange)="tabSelected($event)">
|
||||
<!-- View the submission tab. -->
|
||||
<core-tab [title]="'addon.mod_assign.submission' | translate">
|
||||
<ng-template>
|
||||
|
|
|
@ -16,7 +16,7 @@ import { Component, Input, OnInit, OnDestroy, ViewChild, Optional, ViewChildren,
|
|||
import { NavController } from 'ionic-angular';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { CoreAppProvider } from '@providers/app';
|
||||
import { CoreEventsProvider } from '@providers/events';
|
||||
import { CoreEventsProvider, CoreEventObserver } from '@providers/events';
|
||||
import { CoreGroupsProvider } from '@providers/groups';
|
||||
import { CoreLangProvider } from '@providers/lang';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
|
@ -35,7 +35,9 @@ import {
|
|||
} from '../../providers/assign';
|
||||
import { AddonModAssignHelperProvider } from '../../providers/helper';
|
||||
import { AddonModAssignOfflineProvider } from '../../providers/assign-offline';
|
||||
import { AddonModAssignSync, AddonModAssignSyncProvider } from '../../providers/assign-sync';
|
||||
import { CoreTabsComponent } from '@components/tabs/tabs';
|
||||
import { CoreTabComponent } from '@components/tabs/tab';
|
||||
import { CoreSplitViewComponent } from '@components/split-view/split-view';
|
||||
import { AddonModAssignSubmissionPluginComponent } from '../submission-plugin/submission-plugin';
|
||||
|
||||
|
@ -107,6 +109,7 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy {
|
|||
protected submissionStatusAvailable: boolean; // Whether we were able to retrieve the submission status.
|
||||
protected originalGrades: any = {}; // Object with the original grade data, to check for changes.
|
||||
protected isDestroyed: boolean; // Whether the component has been destroyed.
|
||||
protected syncObserver: CoreEventObserver;
|
||||
|
||||
constructor(protected navCtrl: NavController, protected appProvider: CoreAppProvider, protected domUtils: CoreDomUtilsProvider,
|
||||
sitesProvider: CoreSitesProvider, protected syncProvider: CoreSyncProvider, protected timeUtils: CoreTimeUtilsProvider,
|
||||
|
@ -129,7 +132,29 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy {
|
|||
this.selectedTab = this.showGrade && this.showGrade !== 'false' ? 1 : 0;
|
||||
this.isSubmittedForGrading = !!this.submitId;
|
||||
|
||||
this.loadData();
|
||||
this.loadData(true);
|
||||
|
||||
// Refresh data if this assign is synchronized and it's grading.
|
||||
const events = [AddonModAssignSyncProvider.AUTO_SYNCED, AddonModAssignSyncProvider.MANUAL_SYNCED];
|
||||
|
||||
this.syncObserver = this.eventsProvider.onMultiple(events, async (data) => {
|
||||
// Check that user is grading and this grade wasn't blocked when sync was performed.
|
||||
if (!this.loaded || !this.isGrading || data.gradesBlocked.indexOf(this.submitId) != -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.context == 'submission' && data.submitId == this.submitId) {
|
||||
// Manual sync triggered by this same submission, ignore it.
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't refresh if the user has modified some data.
|
||||
const hasDataToSave = await this.hasDataToSave();
|
||||
|
||||
if (!hasDataToSave) {
|
||||
this.invalidateAndRefresh(false);
|
||||
}
|
||||
}, this.siteId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -241,7 +266,7 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy {
|
|||
}, this.siteId);
|
||||
} else {
|
||||
// Invalidate and refresh data to update this view.
|
||||
this.invalidateAndRefresh();
|
||||
this.invalidateAndRefresh(true);
|
||||
}
|
||||
}).catch((error) => {
|
||||
this.domUtils.showErrorModalDefault(error, 'core.error', true);
|
||||
|
@ -334,9 +359,10 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy {
|
|||
/**
|
||||
* Invalidate and refresh data.
|
||||
*
|
||||
* @param sync Whether to try to synchronize data.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
invalidateAndRefresh(): Promise<any> {
|
||||
invalidateAndRefresh(sync?: boolean): Promise<any> {
|
||||
this.loaded = false;
|
||||
|
||||
const promises = [];
|
||||
|
@ -361,16 +387,17 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy {
|
|||
return Promise.all(promises).catch(() => {
|
||||
// Ignore errors.
|
||||
}).then(() => {
|
||||
return this.loadData();
|
||||
return this.loadData(sync);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the data to render the submission.
|
||||
*
|
||||
* @param sync Whether to try to synchronize data.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected loadData(): Promise<any> {
|
||||
protected async loadData(sync?: boolean): Promise<any> {
|
||||
let isBlind = !!this.blindId;
|
||||
|
||||
this.previousAttempt = undefined;
|
||||
|
@ -381,44 +408,53 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy {
|
|||
isBlind = false;
|
||||
}
|
||||
|
||||
try {
|
||||
// Get the assignment.
|
||||
return this.assignProvider.getAssignment(this.courseId, this.moduleId).then((assign) => {
|
||||
const time = this.timeUtils.timestamp(),
|
||||
promises = [];
|
||||
this.assign = await this.assignProvider.getAssignment(this.courseId, this.moduleId);
|
||||
|
||||
this.assign = assign;
|
||||
if (this.submitId != this.currentUserId && sync) {
|
||||
// Teacher viewing a student submission. Try to sync the assign, there could be offline grades stored.
|
||||
try {
|
||||
const result = await AddonModAssignSync.instance.syncAssign(this.assign.id);
|
||||
|
||||
if (assign.allowsubmissionsfromdate && assign.allowsubmissionsfromdate >= time) {
|
||||
this.fromDate = this.timeUtils.userDate(assign.allowsubmissionsfromdate * 1000);
|
||||
if (result && result.updated) {
|
||||
this.eventsProvider.trigger(AddonModAssignSyncProvider.MANUAL_SYNCED, {
|
||||
assignId: this.assign.id,
|
||||
warnings: result.warnings,
|
||||
gradesBlocked: result.gradesBlocked,
|
||||
context: 'submission',
|
||||
submitId: this.submitId,
|
||||
}, this.siteId);
|
||||
}
|
||||
} catch (error) {
|
||||
// Ignore errors, probably user is offline or sync is blocked.
|
||||
}
|
||||
}
|
||||
|
||||
const time = this.timeUtils.timestamp();
|
||||
let promises = [];
|
||||
|
||||
if (this.assign.allowsubmissionsfromdate && this.assign.allowsubmissionsfromdate >= time) {
|
||||
this.fromDate = this.timeUtils.userDate(this.assign.allowsubmissionsfromdate * 1000);
|
||||
}
|
||||
|
||||
this.currentAttempt = 0;
|
||||
this.maxAttemptsText = this.translate.instant('addon.mod_assign.unlimitedattempts');
|
||||
this.blindMarking = this.isSubmittedForGrading && assign.blindmarking && !assign.revealidentities;
|
||||
this.blindMarking = this.isSubmittedForGrading && this.assign.blindmarking && !this.assign.revealidentities;
|
||||
|
||||
if (!this.blindMarking && this.submitId != this.currentUserId) {
|
||||
promises.push(this.userProvider.getProfile(this.submitId, this.courseId).then((profile) => {
|
||||
this.user = profile;
|
||||
}));
|
||||
promises.push(this.loadSubmissionUserProfile());
|
||||
}
|
||||
|
||||
// Check if there's any offline data for this submission.
|
||||
promises.push(this.assignOfflineProvider.getSubmission(assign.id, this.submitId).then((data) => {
|
||||
this.hasOffline = data && data.plugindata && Object.keys(data.plugindata).length > 0;
|
||||
this.submittedOffline = data && data.submitted;
|
||||
}).catch(() => {
|
||||
// No offline data found.
|
||||
this.hasOffline = false;
|
||||
this.submittedOffline = false;
|
||||
}));
|
||||
promises.push(this.loadSubmissionOfflineData());
|
||||
|
||||
await Promise.all(promises);
|
||||
|
||||
return Promise.all(promises);
|
||||
}).then(() => {
|
||||
// Get submission status.
|
||||
return this.assignProvider.getSubmissionStatusWithRetry(this.assign, this.submitId, undefined, isBlind);
|
||||
}).then((response) => {
|
||||
const response = await this.assignProvider.getSubmissionStatusWithRetry(this.assign, this.submitId, undefined, isBlind);
|
||||
|
||||
const promises = [];
|
||||
promises = [];
|
||||
|
||||
this.submissionStatusAvailable = true;
|
||||
this.lastAttempt = response.lastattempt;
|
||||
|
@ -450,16 +486,41 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
// Get the submission plugins that don't support editing.
|
||||
promises.push(this.assignProvider.getUnsupportedEditPlugins(this.userSubmission.plugins).then((list) => {
|
||||
this.unsupportedEditPlugins = list;
|
||||
}));
|
||||
promises.push(this.loadUnsupportedPlugins());
|
||||
|
||||
return Promise.all(promises);
|
||||
}).catch((error) => {
|
||||
await Promise.all(promises);
|
||||
} catch (error) {
|
||||
this.domUtils.showErrorModalDefault(error, 'Error getting assigment data.');
|
||||
}).finally(() => {
|
||||
} finally {
|
||||
this.loaded = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load profile of submission's user.
|
||||
*
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected async loadSubmissionUserProfile(): Promise<void> {
|
||||
this.user = await this.userProvider.getProfile(this.submitId, this.courseId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load offline data for the submission (not the submission grade).
|
||||
*
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected async loadSubmissionOfflineData(): Promise<void> {
|
||||
try {
|
||||
const data = await this.assignOfflineProvider.getSubmission(this.assign.id, this.submitId);
|
||||
|
||||
this.hasOffline = data && data.plugindata && Object.keys(data.plugindata).length > 0;
|
||||
this.submittedOffline = data && data.submitted;
|
||||
} catch (error) {
|
||||
// No offline data found.
|
||||
this.hasOffline = false;
|
||||
this.submittedOffline = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -537,11 +598,6 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy {
|
|||
// Make sure outcomes is an array.
|
||||
gradeInfo.outcomes = gradeInfo.outcomes || [];
|
||||
|
||||
if (!this.isDestroyed) {
|
||||
// Block the assignment.
|
||||
this.syncProvider.blockOperation(AddonModAssignProvider.COMPONENT, this.assign.id);
|
||||
}
|
||||
|
||||
// Treat the grade info.
|
||||
return this.treatGradeInfo();
|
||||
}).then(() => {
|
||||
|
@ -627,6 +683,15 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the submission plugins that don't support editing.
|
||||
*
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected async loadUnsupportedPlugins(): Promise<void> {
|
||||
this.unsupportedEditPlugins = await this.assignProvider.getUnsupportedEditPlugins(this.userSubmission.plugins);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the submission status name and class.
|
||||
*
|
||||
|
@ -764,7 +829,7 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy {
|
|||
return this.discardDrafts();
|
||||
}).finally(() => {
|
||||
// Invalidate and refresh data.
|
||||
this.invalidateAndRefresh();
|
||||
this.invalidateAndRefresh(true);
|
||||
|
||||
this.eventsProvider.trigger(AddonModAssignProvider.GRADED_EVENT, {
|
||||
assignmentId: this.assign.id,
|
||||
|
@ -952,15 +1017,42 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Block or unblock the automatic sync of the user grade.
|
||||
*
|
||||
* @param block Whether to block or unblock.
|
||||
*/
|
||||
protected setGradeSyncBlocked(block?: boolean): void {
|
||||
if (this.isDestroyed || !this.assign || !this.isGrading) {
|
||||
return;
|
||||
}
|
||||
|
||||
const syncId = AddonModAssignSync.instance.getGradeSyncId(this.assign.id, this.submitId);
|
||||
|
||||
if (block) {
|
||||
this.syncProvider.blockOperation(AddonModAssignProvider.COMPONENT, syncId);
|
||||
} else {
|
||||
this.syncProvider.unblockOperation(AddonModAssignProvider.COMPONENT, syncId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A certain tab has been selected, either manually or automatically.
|
||||
*
|
||||
* @param tab The tab that was selected.
|
||||
*/
|
||||
tabSelected(tab: CoreTabComponent): void {
|
||||
// Block sync when selecting grade tab, unblock when leaving it.
|
||||
this.setGradeSyncBlocked(this.tabs.getIndex(tab) === 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Component being destroyed.
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.setGradeSyncBlocked(false);
|
||||
this.isDestroyed = true;
|
||||
|
||||
if (this.assign && this.isGrading) {
|
||||
this.syncProvider.unblockOperation(AddonModAssignProvider.COMPONENT, this.assign.id);
|
||||
}
|
||||
this.syncObserver && this.syncObserver.off();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -89,6 +89,7 @@
|
|||
"submitassignment": "Submit assignment",
|
||||
"submittedearly": "Assignment was submitted {{$a}} early",
|
||||
"submittedlate": "Assignment was submitted {{$a}} late",
|
||||
"syncblockedusercomponent": "user grade",
|
||||
"timemodified": "Last modified",
|
||||
"timeremaining": "Time remaining",
|
||||
"ungroupedusers": "The setting 'Require group to make submission' is enabled and some users are either not a member of any group, or are a member of more than one group, so are unable to make submissions.",
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core';
|
||||
import { IonicPage, NavParams } from 'ionic-angular';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { CoreEventsProvider } from '@providers/events';
|
||||
import { CoreEventsProvider, CoreEventObserver } from '@providers/events';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
import { CoreGroupsProvider, CoreGroupInfo } from '@providers/groups';
|
||||
|
@ -23,6 +23,7 @@ import {
|
|||
AddonModAssignProvider, AddonModAssignAssign, AddonModAssignGrade, AddonModAssignSubmission
|
||||
} from '../../providers/assign';
|
||||
import { AddonModAssignOfflineProvider } from '../../providers/assign-offline';
|
||||
import { AddonModAssignSyncProvider, AddonModAssignSync } from '../../providers/assign-sync';
|
||||
import { AddonModAssignHelperProvider, AddonModAssignSubmissionFormatted } from '../../providers/helper';
|
||||
import { CoreSplitViewComponent } from '@components/split-view/split-view';
|
||||
|
||||
|
@ -54,10 +55,11 @@ export class AddonModAssignSubmissionListPage implements OnInit, OnDestroy {
|
|||
protected moduleId: number; // Module ID the submission belongs to.
|
||||
protected courseId: number; // Course ID the assignment belongs to.
|
||||
protected selectedStatus: string; // The status to see.
|
||||
protected gradedObserver; // Observer to refresh data when a grade changes.
|
||||
protected gradedObserver: CoreEventObserver; // Observer to refresh data when a grade changes.
|
||||
protected syncObserver: CoreEventObserver; // OObserver to refresh data when the async is synchronized.
|
||||
protected submissionsData: {canviewsubmissions: boolean, submissions?: AddonModAssignSubmission[]};
|
||||
|
||||
constructor(navParams: NavParams, protected sitesProvider: CoreSitesProvider, eventsProvider: CoreEventsProvider,
|
||||
constructor(navParams: NavParams, protected sitesProvider: CoreSitesProvider, protected eventsProvider: CoreEventsProvider,
|
||||
protected domUtils: CoreDomUtilsProvider, protected translate: TranslateService,
|
||||
protected assignProvider: AddonModAssignProvider, protected assignOfflineProvider: AddonModAssignOfflineProvider,
|
||||
protected assignHelper: AddonModAssignHelperProvider, protected groupsProvider: CoreGroupsProvider) {
|
||||
|
@ -79,22 +81,37 @@ export class AddonModAssignSubmissionListPage implements OnInit, OnDestroy {
|
|||
|
||||
// Update data if some grade changes.
|
||||
this.gradedObserver = eventsProvider.on(AddonModAssignProvider.GRADED_EVENT, (data) => {
|
||||
if (this.assign && data.assignmentId == this.assign.id && data.userId == sitesProvider.getCurrentSiteUserId()) {
|
||||
if (this.loaded && this.assign && data.assignmentId == this.assign.id &&
|
||||
data.userId == sitesProvider.getCurrentSiteUserId()) {
|
||||
// Grade changed, refresh the data.
|
||||
this.loaded = false;
|
||||
|
||||
this.refreshAllData().finally(() => {
|
||||
this.refreshAllData(true).finally(() => {
|
||||
this.loaded = true;
|
||||
});
|
||||
}
|
||||
}, sitesProvider.getCurrentSiteId());
|
||||
|
||||
// Refresh data if this assign is synchronized.
|
||||
const events = [AddonModAssignSyncProvider.AUTO_SYNCED, AddonModAssignSyncProvider.MANUAL_SYNCED];
|
||||
this.syncObserver = eventsProvider.onMultiple(events, (data) => {
|
||||
if (!this.loaded || data.context == 'submission-list') {
|
||||
return;
|
||||
}
|
||||
|
||||
this.loaded = false;
|
||||
|
||||
this.refreshAllData(false).finally(() => {
|
||||
this.loaded = true;
|
||||
});
|
||||
}, this.sitesProvider.getCurrentSiteId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Component being initialized.
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.fetchAssignment().finally(() => {
|
||||
this.fetchAssignment(true).finally(() => {
|
||||
if (!this.selectedSubmissionId && this.splitviewCtrl.isOn() && this.submissions.length > 0) {
|
||||
// Take first and load it.
|
||||
this.loadSubmission(this.submissions[0]);
|
||||
|
@ -107,34 +124,49 @@ export class AddonModAssignSubmissionListPage implements OnInit, OnDestroy {
|
|||
/**
|
||||
* Fetch assignment data.
|
||||
*
|
||||
* @param sync Whether to try to synchronize data.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected fetchAssignment(): Promise<any> {
|
||||
|
||||
protected async fetchAssignment(sync?: boolean): Promise<void> {
|
||||
try {
|
||||
// Get assignment data.
|
||||
return this.assignProvider.getAssignment(this.courseId, this.moduleId).then((assign) => {
|
||||
this.title = assign.name || this.title;
|
||||
this.assign = assign;
|
||||
this.assign = await this.assignProvider.getAssignment(this.courseId, this.moduleId);
|
||||
|
||||
// Get assignment submissions.
|
||||
return this.assignProvider.getSubmissions(assign.id);
|
||||
}).then((data) => {
|
||||
if (!data.canviewsubmissions) {
|
||||
// User shouldn't be able to reach here.
|
||||
return Promise.reject(null);
|
||||
this.title = this.assign.name || this.title;
|
||||
|
||||
if (sync) {
|
||||
try {
|
||||
// Try to synchronize data.
|
||||
const result = await AddonModAssignSync.instance.syncAssign(this.assign.id);
|
||||
|
||||
if (result && result.updated) {
|
||||
this.eventsProvider.trigger(AddonModAssignSyncProvider.MANUAL_SYNCED, {
|
||||
assignId: this.assign.id,
|
||||
warnings: result.warnings,
|
||||
gradesBlocked: result.gradesBlocked,
|
||||
context: 'submission-list',
|
||||
}, this.sitesProvider.getCurrentSiteId());
|
||||
}
|
||||
} catch (error) {
|
||||
// Ignore errors, probably user is offline or sync is blocked.
|
||||
}
|
||||
}
|
||||
|
||||
this.submissionsData = data;
|
||||
// Get assignment submissions.
|
||||
this.submissionsData = await this.assignProvider.getSubmissions(this.assign.id);
|
||||
|
||||
if (!this.submissionsData.canviewsubmissions) {
|
||||
// User shouldn't be able to reach here.
|
||||
throw new Error('Cannot view submissions.');
|
||||
}
|
||||
|
||||
// Check if groupmode is enabled to avoid showing wrong numbers.
|
||||
return this.groupsProvider.getActivityGroupInfo(this.assign.cmid, false).then((groupInfo) => {
|
||||
this.groupInfo = groupInfo;
|
||||
this.groupInfo = await this.groupsProvider.getActivityGroupInfo(this.assign.cmid, false);
|
||||
|
||||
return this.setGroup(this.groupsProvider.validateGroupId(this.groupId, groupInfo));
|
||||
});
|
||||
}).catch((error) => {
|
||||
await this.setGroup(this.groupsProvider.validateGroupId(this.groupId, this.groupInfo));
|
||||
} catch (error) {
|
||||
this.domUtils.showErrorModalDefault(error, 'Error getting assigment data.');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -265,9 +297,10 @@ export class AddonModAssignSubmissionListPage implements OnInit, OnDestroy {
|
|||
/**
|
||||
* Refresh all the data.
|
||||
*
|
||||
* @param sync Whether to try to synchronize data.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected refreshAllData(): Promise<any> {
|
||||
protected refreshAllData(sync?: boolean): Promise<any> {
|
||||
const promises = [];
|
||||
|
||||
promises.push(this.assignProvider.invalidateAssignmentData(this.courseId));
|
||||
|
@ -279,7 +312,7 @@ export class AddonModAssignSubmissionListPage implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
return Promise.all(promises).finally(() => {
|
||||
return this.fetchAssignment();
|
||||
return this.fetchAssignment(sync);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -289,7 +322,7 @@ export class AddonModAssignSubmissionListPage implements OnInit, OnDestroy {
|
|||
* @param refresher Refresher.
|
||||
*/
|
||||
refreshList(refresher: any): void {
|
||||
this.refreshAllData().finally(() => {
|
||||
this.refreshAllData(true).finally(() => {
|
||||
refresher.complete();
|
||||
});
|
||||
}
|
||||
|
@ -299,6 +332,7 @@ export class AddonModAssignSubmissionListPage implements OnInit, OnDestroy {
|
|||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.gradedObserver && this.gradedObserver.off();
|
||||
this.syncObserver && this.syncObserver.off();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -137,7 +137,7 @@ export class AddonModAssignSubmissionReviewPage implements OnInit {
|
|||
}
|
||||
|
||||
return Promise.all(promises).finally(() => {
|
||||
this.submissionComponent && this.submissionComponent.invalidateAndRefresh();
|
||||
this.submissionComponent && this.submissionComponent.invalidateAndRefresh(true);
|
||||
|
||||
return this.fetchSubmission();
|
||||
});
|
||||
|
|
|
@ -25,12 +25,14 @@ import { CoreUtilsProvider } from '@providers/utils/utils';
|
|||
import { CoreCourseProvider } from '@core/course/providers/course';
|
||||
import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper';
|
||||
import { CoreGradesHelperProvider } from '@core/grades/providers/helper';
|
||||
import { CoreSyncBaseProvider } from '@classes/base-sync';
|
||||
import { AddonModAssignProvider, AddonModAssignAssign } from './assign';
|
||||
import { CoreSyncBaseProvider, CoreSyncBlockedError } from '@classes/base-sync';
|
||||
import { AddonModAssignProvider, AddonModAssignAssign, AddonModAssignSubmission } from './assign';
|
||||
import { AddonModAssignOfflineProvider } from './assign-offline';
|
||||
import { AddonModAssignSubmissionDelegate } from './submission-delegate';
|
||||
import { AddonModAssignFeedbackDelegate } from './feedback-delegate';
|
||||
|
||||
import { makeSingleton } from '@singletons/core.singletons';
|
||||
|
||||
/**
|
||||
* Data returned by an assign sync.
|
||||
*/
|
||||
|
@ -44,6 +46,11 @@ export interface AddonModAssignSyncResult {
|
|||
* Whether data was updated in the site.
|
||||
*/
|
||||
updated: boolean;
|
||||
|
||||
/**
|
||||
* Whether some grade couldn't be synced because it was blocked.
|
||||
*/
|
||||
gradesBlocked: number[];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -53,6 +60,7 @@ export interface AddonModAssignSyncResult {
|
|||
export class AddonModAssignSyncProvider extends CoreSyncBaseProvider {
|
||||
|
||||
static AUTO_SYNCED = 'addon_mod_assign_autom_synced';
|
||||
static MANUAL_SYNCED = 'addon_mod_assign_manual_synced';
|
||||
|
||||
protected componentTranslate: string;
|
||||
|
||||
|
@ -79,6 +87,17 @@ export class AddonModAssignSyncProvider extends CoreSyncBaseProvider {
|
|||
this.componentTranslate = courseProvider.translateModuleName('assign');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the sync ID for a certain user grade.
|
||||
*
|
||||
* @param assignId Assign ID.
|
||||
* @param userId User the grade belongs to.
|
||||
* @return Sync ID.
|
||||
*/
|
||||
getGradeSyncId(assignId: number, userId: number): string {
|
||||
return 'assignGrade#' + assignId + '#' + userId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience function to get scale selected option.
|
||||
*
|
||||
|
@ -121,7 +140,7 @@ export class AddonModAssignSyncProvider extends CoreSyncBaseProvider {
|
|||
* @param force Wether to force sync not depending on last execution.
|
||||
* @return Promise resolved if sync is successful, rejected if sync fails.
|
||||
*/
|
||||
syncAllAssignments(siteId?: string, force?: boolean): Promise<any> {
|
||||
syncAllAssignments(siteId?: string, force?: boolean): Promise<void> {
|
||||
return this.syncOnSites('all assignments', this.syncAllAssignmentsFunc.bind(this), [force], siteId);
|
||||
}
|
||||
|
||||
|
@ -132,26 +151,25 @@ export class AddonModAssignSyncProvider extends CoreSyncBaseProvider {
|
|||
* @param force Wether to force sync not depending on last execution.
|
||||
* @param Promise resolved if sync is successful, rejected if sync fails.
|
||||
*/
|
||||
protected syncAllAssignmentsFunc(siteId?: string, force?: boolean): Promise<any> {
|
||||
protected async syncAllAssignmentsFunc(siteId?: string, force?: boolean): Promise<void> {
|
||||
// Get all assignments that have offline data.
|
||||
return this.assignOfflineProvider.getAllAssigns(siteId).then((assignIds) => {
|
||||
// Sync all assignments that haven't been synced for a while.
|
||||
const promises = assignIds.map((assignId) => {
|
||||
const promise = force ? this.syncAssign(assignId, siteId) : this.syncAssignIfNeeded(assignId, siteId);
|
||||
const assignIds = await this.assignOfflineProvider.getAllAssigns(siteId);
|
||||
|
||||
// Try to sync all assignments.
|
||||
await Promise.all(assignIds.map(async (assignId) => {
|
||||
const data = force ? await this.syncAssign(assignId, siteId) : await this.syncAssignIfNeeded(assignId, siteId);
|
||||
|
||||
if (!data || !data.updated) {
|
||||
// Not updated.
|
||||
return;
|
||||
}
|
||||
|
||||
return promise.then((data) => {
|
||||
if (data && data.updated) {
|
||||
// Sync done. Send event.
|
||||
this.eventsProvider.trigger(AddonModAssignSyncProvider.AUTO_SYNCED, {
|
||||
assignId: assignId,
|
||||
warnings: data.warnings
|
||||
warnings: data.warnings,
|
||||
gradesBlocked: data.gradesBlocked,
|
||||
}, siteId);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return Promise.all(promises);
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -161,12 +179,12 @@ export class AddonModAssignSyncProvider extends CoreSyncBaseProvider {
|
|||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved when the assign is synced or it doesn't need to be synced.
|
||||
*/
|
||||
syncAssignIfNeeded(assignId: number, siteId?: string): Promise<void | AddonModAssignSyncResult> {
|
||||
return this.isSyncNeeded(assignId, siteId).then((needed) => {
|
||||
async syncAssignIfNeeded(assignId: number, siteId?: string): Promise<void | AddonModAssignSyncResult> {
|
||||
const needed = await this.isSyncNeeded(assignId, siteId);
|
||||
|
||||
if (needed) {
|
||||
return this.syncAssign(assignId, siteId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -176,18 +194,9 @@ export class AddonModAssignSyncProvider extends CoreSyncBaseProvider {
|
|||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved in success.
|
||||
*/
|
||||
syncAssign(assignId: number, siteId?: string): Promise<AddonModAssignSyncResult> {
|
||||
async syncAssign(assignId: number, siteId?: string): Promise<AddonModAssignSyncResult> {
|
||||
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||
|
||||
const promises: Promise<any>[] = [],
|
||||
result: AddonModAssignSyncResult = {
|
||||
warnings: [],
|
||||
updated: false
|
||||
};
|
||||
let assign: AddonModAssignAssign,
|
||||
courseId: number,
|
||||
syncPromise: Promise<any>;
|
||||
|
||||
if (this.isSyncing(assignId, siteId)) {
|
||||
// There's already a sync ongoing for this assign, return the promise.
|
||||
return this.getOngoingSync(assignId, siteId);
|
||||
|
@ -195,79 +204,126 @@ export class AddonModAssignSyncProvider extends CoreSyncBaseProvider {
|
|||
|
||||
// Verify that assign isn't blocked.
|
||||
if (this.syncProvider.isBlocked(AddonModAssignProvider.COMPONENT, assignId, siteId)) {
|
||||
this.logger.debug('Cannot sync assign ' + assignId + ' because it is blocked.');
|
||||
this.logger.error('Cannot sync assign ' + assignId + ' because it is blocked.');
|
||||
|
||||
return Promise.reject(this.translate.instant('core.errorsyncblocked', {$a: this.componentTranslate}));
|
||||
throw new CoreSyncBlockedError(this.translate.instant('core.errorsyncblocked', {$a: this.componentTranslate}));
|
||||
}
|
||||
|
||||
this.logger.debug('Try to sync assign ' + assignId + ' in site ' + siteId);
|
||||
return this.addOngoingSync(assignId, this.performSyncAssign(assignId, siteId), siteId);
|
||||
}
|
||||
|
||||
// Get offline submissions to be sent.
|
||||
promises.push(this.assignOfflineProvider.getAssignSubmissions(assignId, siteId).catch(() => {
|
||||
// No offline data found, return empty array.
|
||||
return [];
|
||||
}));
|
||||
/**
|
||||
* Perform the assign submission.
|
||||
*
|
||||
* @param assignId Assign ID.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved in success.
|
||||
*/
|
||||
protected async performSyncAssign(assignId: number, siteId?: string): Promise<AddonModAssignSyncResult> {
|
||||
|
||||
// Get offline submission grades to be sent.
|
||||
promises.push(this.assignOfflineProvider.getAssignSubmissionsGrade(assignId, siteId).catch(() => {
|
||||
// No offline data found, return empty array.
|
||||
return [];
|
||||
}));
|
||||
this.logger.error('Try to sync assign ' + assignId + ' in site ' + siteId);
|
||||
|
||||
// Sync offline logs.
|
||||
promises.push(this.logHelper.syncIfNeeded(AddonModAssignProvider.COMPONENT, assignId, siteId));
|
||||
const result: AddonModAssignSyncResult = {
|
||||
warnings: [],
|
||||
updated: false,
|
||||
gradesBlocked: [],
|
||||
};
|
||||
|
||||
syncPromise = Promise.all(promises).then((results) => {
|
||||
const submissions = results[0],
|
||||
grades = results[1];
|
||||
// Load offline data and sync offline logs.
|
||||
const promisesResults = await Promise.all([
|
||||
this.getOfflineSubmissions(assignId, siteId),
|
||||
this.getOfflineGrades(assignId, siteId),
|
||||
this.logHelper.syncIfNeeded(AddonModAssignProvider.COMPONENT, assignId, siteId),
|
||||
]);
|
||||
|
||||
const submissions = promisesResults[0];
|
||||
const grades = promisesResults[1];
|
||||
|
||||
if (!submissions.length && !grades.length) {
|
||||
// Nothing to sync.
|
||||
return;
|
||||
await this.utils.ignoreErrors(this.setSyncTime(assignId, siteId));
|
||||
|
||||
return result;
|
||||
} else if (!this.appProvider.isOnline()) {
|
||||
// Cannot sync in offline.
|
||||
return Promise.reject(null);
|
||||
throw new Error(this.translate.instant('core.cannotconnect'));
|
||||
}
|
||||
|
||||
courseId = submissions.length > 0 ? submissions[0].courseid : grades[0].courseid;
|
||||
const courseId = submissions.length > 0 ? submissions[0].courseid : grades[0].courseid;
|
||||
|
||||
return this.assignProvider.getAssignmentById(courseId, assignId, false, siteId).then((assignData) => {
|
||||
assign = assignData;
|
||||
const assign = await this.assignProvider.getAssignmentById(courseId, assignId, false, siteId);
|
||||
|
||||
const promises = [];
|
||||
let promises = [];
|
||||
|
||||
promises = promises.concat(submissions.map(async (submission) => {
|
||||
await this.syncSubmission(assign, submission, result.warnings, siteId);
|
||||
|
||||
submissions.forEach((submission) => {
|
||||
promises.push(this.syncSubmission(assign, submission, result.warnings, siteId).then(() => {
|
||||
result.updated = true;
|
||||
}));
|
||||
});
|
||||
|
||||
grades.forEach((grade) => {
|
||||
promises.push(this.syncSubmissionGrade(assign, grade, result.warnings, courseId, siteId).then(() => {
|
||||
promises = promises.concat(grades.map(async (grade) => {
|
||||
try {
|
||||
await this.syncSubmissionGrade(assign, grade, result.warnings, courseId, siteId);
|
||||
|
||||
result.updated = true;
|
||||
} catch (error) {
|
||||
if (error instanceof CoreSyncBlockedError) {
|
||||
// Grade blocked, but allow finish the sync.
|
||||
result.gradesBlocked.push(grade.userid);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}));
|
||||
});
|
||||
|
||||
return Promise.all(promises);
|
||||
}).then(() => {
|
||||
await Promise.all(promises);
|
||||
|
||||
if (result.updated) {
|
||||
// Data has been sent to server. Now invalidate the WS calls.
|
||||
return this.assignProvider.invalidateContent(assign.cmid, courseId, siteId).catch(() => {
|
||||
// Ignore errors.
|
||||
});
|
||||
await this.utils.ignoreErrors(this.assignProvider.invalidateContent(assign.cmid, courseId, siteId));
|
||||
}
|
||||
});
|
||||
}).then(() => {
|
||||
|
||||
// Sync finished, set sync time.
|
||||
return this.setSyncTime(assignId, siteId).catch(() => {
|
||||
// Ignore errors.
|
||||
});
|
||||
}).then(() => {
|
||||
await this.utils.ignoreErrors(this.setSyncTime(assignId, siteId));
|
||||
|
||||
// All done, return the result.
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
return this.addOngoingSync(assignId, syncPromise, siteId);
|
||||
/**
|
||||
* Get offline grades to be sent.
|
||||
*
|
||||
* @param assignId Assign ID.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise with grades.
|
||||
*/
|
||||
protected async getOfflineGrades(assignId: number, siteId: string): Promise<any[]> {
|
||||
try {
|
||||
const submissions = await this.assignOfflineProvider.getAssignSubmissionsGrade(assignId, siteId);
|
||||
|
||||
return submissions;
|
||||
} catch (error) {
|
||||
// No offline data found, return empty array.
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get offline submissions to be sent.
|
||||
*
|
||||
* @param assignId Assign ID.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise with submissions.
|
||||
*/
|
||||
protected async getOfflineSubmissions(assignId: number, siteId: string): Promise<any[]> {
|
||||
try {
|
||||
const submissions = await this.assignOfflineProvider.getAssignSubmissions(assignId, siteId);
|
||||
|
||||
return submissions;
|
||||
} catch (error) {
|
||||
// No offline data found, return empty array.
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -279,83 +335,76 @@ export class AddonModAssignSyncProvider extends CoreSyncBaseProvider {
|
|||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved if success, rejected otherwise.
|
||||
*/
|
||||
protected syncSubmission(assign: AddonModAssignAssign, offlineData: any, warnings: string[], siteId?: string): Promise<any> {
|
||||
const userId = offlineData.userid,
|
||||
pluginData = {};
|
||||
let discardError,
|
||||
submission;
|
||||
protected async syncSubmission(assign: AddonModAssignAssign, offlineData: any, warnings: string[], siteId?: string)
|
||||
: Promise<void> {
|
||||
|
||||
return this.assignProvider.getSubmissionStatus(assign.id, userId, undefined, false, true, true, siteId).then((status) => {
|
||||
const promises = [];
|
||||
const userId = offlineData.userid;
|
||||
const pluginData = {};
|
||||
|
||||
submission = this.assignProvider.getSubmissionObjectFromAttempt(assign, status.lastattempt);
|
||||
const status = await this.assignProvider.getSubmissionStatus(assign.id, userId, undefined, false, true, true, siteId);
|
||||
|
||||
const submission = this.assignProvider.getSubmissionObjectFromAttempt(assign, status.lastattempt);
|
||||
|
||||
if (submission.timemodified != offlineData.onlinetimemodified) {
|
||||
// The submission was modified in Moodle, discard the submission.
|
||||
discardError = this.translate.instant('addon.mod_assign.warningsubmissionmodified');
|
||||
this.addOfflineDataDeletedWarning(warnings, this.componentTranslate, assign.name,
|
||||
this.translate.instant('addon.mod_assign.warningsubmissionmodified'));
|
||||
|
||||
return;
|
||||
return this.deleteSubmissionData(assign, submission, offlineData, siteId);
|
||||
}
|
||||
|
||||
submission.plugins.forEach((plugin) => {
|
||||
promises.push(this.submissionDelegate.preparePluginSyncData(assign, submission, plugin, offlineData, pluginData,
|
||||
siteId));
|
||||
});
|
||||
try {
|
||||
// Prepare plugins data.
|
||||
await Promise.all(submission.plugins.map(async (plugin) => {
|
||||
await this.submissionDelegate.preparePluginSyncData(assign, submission, plugin, offlineData, pluginData, siteId);
|
||||
}));
|
||||
|
||||
return Promise.all(promises).then(() => {
|
||||
// Now save the submission.
|
||||
let promise;
|
||||
|
||||
if (!Object.keys(pluginData).length) {
|
||||
// Nothing to save.
|
||||
promise = Promise.resolve();
|
||||
} else {
|
||||
promise = this.assignProvider.saveSubmissionOnline(assign.id, pluginData, siteId);
|
||||
if (Object.keys(pluginData).length > 0) {
|
||||
await this.assignProvider.saveSubmissionOnline(assign.id, pluginData, siteId);
|
||||
}
|
||||
|
||||
return promise.then(() => {
|
||||
if (assign.submissiondrafts && offlineData.submitted) {
|
||||
// The user submitted the assign manually. Submit it for grading.
|
||||
return this.assignProvider.submitForGradingOnline(assign.id, offlineData.submissionstatement, siteId);
|
||||
await this.assignProvider.submitForGradingOnline(assign.id, offlineData.submissionstatement, siteId);
|
||||
}
|
||||
}).then(() => {
|
||||
|
||||
// Submission data sent, update cached data. No need to block the user for this.
|
||||
this.assignProvider.getSubmissionStatus(assign.id, userId, undefined, false, true, true, siteId);
|
||||
});
|
||||
}).catch((error) => {
|
||||
if (error && this.utils.isWebServiceError(error)) {
|
||||
} catch (error) {
|
||||
if (!error || !this.utils.isWebServiceError(error)) {
|
||||
// Local error, reject.
|
||||
throw error;
|
||||
}
|
||||
|
||||
// A WebService has thrown an error, this means it cannot be submitted. Discard the submission.
|
||||
discardError = this.textUtils.getErrorMessageFromError(error);
|
||||
} else {
|
||||
// Couldn't connect to server, reject.
|
||||
return Promise.reject(error);
|
||||
this.addOfflineDataDeletedWarning(warnings, this.componentTranslate, assign.name,
|
||||
this.textUtils.getErrorMessageFromError(error));
|
||||
}
|
||||
});
|
||||
}).then(() => {
|
||||
|
||||
// Delete the offline data.
|
||||
return this.assignOfflineProvider.deleteSubmission(assign.id, userId, siteId).then(() => {
|
||||
const promises = [];
|
||||
|
||||
submission.plugins.forEach((plugin) => {
|
||||
promises.push(this.submissionDelegate.deletePluginOfflineData(assign, submission, plugin, offlineData, siteId));
|
||||
});
|
||||
|
||||
return Promise.all(promises);
|
||||
});
|
||||
}).then(() => {
|
||||
if (discardError) {
|
||||
// Submission was discarded, add a warning.
|
||||
const message = this.translate.instant('core.warningofflinedatadeleted', {
|
||||
component: this.componentTranslate,
|
||||
name: assign.name,
|
||||
error: discardError
|
||||
});
|
||||
|
||||
if (warnings.indexOf(message) == -1) {
|
||||
warnings.push(message);
|
||||
await this.deleteSubmissionData(assign, submission, offlineData, siteId);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Delete the submission offline data (not grades).
|
||||
*
|
||||
* @param assign Assign.
|
||||
* @param submission Submission.
|
||||
* @param offlineData Offline data.
|
||||
* @param siteId Site ID.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected async deleteSubmissionData(assign: AddonModAssignAssign, submission: AddonModAssignSubmission, offlineData: any,
|
||||
siteId?: string): Promise<void> {
|
||||
|
||||
// Delete the offline data.
|
||||
await this.assignOfflineProvider.deleteSubmission(assign.id, offlineData.userid, siteId);
|
||||
|
||||
// Delete plugins data.
|
||||
await Promise.all(submission.plugins.map(async (plugin) => {
|
||||
await this.submissionDelegate.deletePluginOfflineData(assign, submission, plugin, offlineData, siteId);
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -368,25 +417,36 @@ export class AddonModAssignSyncProvider extends CoreSyncBaseProvider {
|
|||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved if success, rejected otherwise.
|
||||
*/
|
||||
protected syncSubmissionGrade(assign: AddonModAssignAssign, offlineData: any, warnings: string[], courseId: number,
|
||||
protected async syncSubmissionGrade(assign: AddonModAssignAssign, offlineData: any, warnings: string[], courseId: number,
|
||||
siteId?: string): Promise<any> {
|
||||
|
||||
const userId = offlineData.userid;
|
||||
let discardError;
|
||||
const syncId = this.getGradeSyncId(assign.id, userId);
|
||||
|
||||
// Check if this grade sync is blocked.
|
||||
if (this.syncProvider.isBlocked(AddonModAssignProvider.COMPONENT, syncId, siteId)) {
|
||||
this.logger.error(`Cannot sync grade for assign ${assign.id} and user ${userId} because it is blocked.!!!!`);
|
||||
|
||||
throw new CoreSyncBlockedError(this.translate.instant('core.errorsyncblocked',
|
||||
{$a: this.translate.instant('addon.mod_assign.syncblockedusercomponent')}));
|
||||
}
|
||||
|
||||
const status = await this.assignProvider.getSubmissionStatus(assign.id, userId, undefined, false, true, true, siteId);
|
||||
|
||||
return this.assignProvider.getSubmissionStatus(assign.id, userId, undefined, false, true, true, siteId).then((status) => {
|
||||
const timemodified = status.feedback && (status.feedback.gradeddate || status.feedback.grade.timemodified);
|
||||
|
||||
if (timemodified > offlineData.timemodified) {
|
||||
// The submission grade was modified in Moodle, discard it.
|
||||
discardError = this.translate.instant('addon.mod_assign.warningsubmissiongrademodified');
|
||||
this.addOfflineDataDeletedWarning(warnings, this.componentTranslate, assign.name,
|
||||
this.translate.instant('addon.mod_assign.warningsubmissiongrademodified'));
|
||||
|
||||
return;
|
||||
return this.assignOfflineProvider.deleteSubmissionGrade(assign.id, userId, siteId);
|
||||
}
|
||||
|
||||
// If grade has been modified from gradebook, do not use offline.
|
||||
return this.gradesHelper.getGradeModuleItems(courseId, assign.cmid, userId, undefined, siteId, true).then((grades) => {
|
||||
return this.courseProvider.getModuleBasicGradeInfo(assign.cmid, siteId).then((gradeInfo) => {
|
||||
const grades = await this.gradesHelper.getGradeModuleItems(courseId, assign.cmid, userId, undefined, siteId, true);
|
||||
|
||||
const gradeInfo = await this.courseProvider.getModuleBasicGradeInfo(assign.cmid, siteId);
|
||||
|
||||
// Override offline grade and outcomes based on the gradebook data.
|
||||
grades.forEach((grade) => {
|
||||
|
@ -407,14 +467,14 @@ export class AddonModAssignSyncProvider extends CoreSyncBaseProvider {
|
|||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}).then(() => {
|
||||
|
||||
try {
|
||||
// Now submit the grade.
|
||||
return this.assignProvider.submitGradingFormOnline(assign.id, userId, offlineData.grade, offlineData.attemptnumber,
|
||||
await this.assignProvider.submitGradingFormOnline(assign.id, userId, offlineData.grade, offlineData.attemptnumber,
|
||||
offlineData.addattempt, offlineData.workflowstate, offlineData.applytoall, offlineData.outcomes,
|
||||
offlineData.plugindata, siteId).then(() => {
|
||||
// Grades sent.
|
||||
// Discard grades drafts.
|
||||
offlineData.plugindata, siteId);
|
||||
|
||||
// Grades sent. Discard grades drafts.
|
||||
const promises = [];
|
||||
if (status.feedback && status.feedback.plugins) {
|
||||
status.feedback.plugins.forEach((plugin) => {
|
||||
|
@ -425,33 +485,21 @@ export class AddonModAssignSyncProvider extends CoreSyncBaseProvider {
|
|||
// Update cached data.
|
||||
promises.push(this.assignProvider.getSubmissionStatus(assign.id, userId, undefined, false, true, true, siteId));
|
||||
|
||||
return Promise.all(promises);
|
||||
}).catch((error) => {
|
||||
if (error && this.utils.isWebServiceError(error)) {
|
||||
// The WebService has thrown an error, this means it cannot be submitted. Discard the offline data.
|
||||
discardError = this.textUtils.getErrorMessageFromError(error);
|
||||
} else {
|
||||
// Couldn't connect to server, reject.
|
||||
return Promise.reject(error);
|
||||
await Promise.all(promises);
|
||||
} catch (error) {
|
||||
if (!error || !this.utils.isWebServiceError(error)) {
|
||||
// Local error, reject.
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
});
|
||||
}).then(() => {
|
||||
// Delete the offline data.
|
||||
return this.assignOfflineProvider.deleteSubmissionGrade(assign.id, userId, siteId);
|
||||
}).then(() => {
|
||||
if (discardError) {
|
||||
// Submission grade was discarded, add a warning.
|
||||
const message = this.translate.instant('core.warningofflinedatadeleted', {
|
||||
component: this.componentTranslate,
|
||||
name: assign.name,
|
||||
error: discardError
|
||||
});
|
||||
|
||||
if (warnings.indexOf(message) == -1) {
|
||||
warnings.push(message);
|
||||
// A WebService has thrown an error, this means it cannot be submitted. Discard the submission.
|
||||
this.addOfflineDataDeletedWarning(warnings, this.componentTranslate, assign.name,
|
||||
this.textUtils.getErrorMessageFromError(error));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Delete the offline data.
|
||||
await this.assignOfflineProvider.deleteSubmissionGrade(assign.id, userId, siteId);
|
||||
}
|
||||
}
|
||||
|
||||
export class AddonModAssignSync extends makeSingleton(AddonModAssignSyncProvider) {}
|
||||
|
|
|
@ -408,6 +408,7 @@
|
|||
"addon.mod_assign.submitassignment_help": "Once this assignment is submitted you will not be able to make any more changes.",
|
||||
"addon.mod_assign.submittedearly": "Assignment was submitted {{$a}} early",
|
||||
"addon.mod_assign.submittedlate": "Assignment was submitted {{$a}} late",
|
||||
"addon.mod_assign.syncblockedusercomponent": "user grade",
|
||||
"addon.mod_assign.timemodified": "Last modified",
|
||||
"addon.mod_assign.timeremaining": "Time remaining",
|
||||
"addon.mod_assign.ungroupedusers": "The setting 'Require group to make submission' is enabled and some users are either not a member of any group, or are a member of more than one group, so are unable to make submissions.",
|
||||
|
|
|
@ -20,6 +20,18 @@ import { CoreAppProvider } from '@providers/app';
|
|||
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
||||
import { CoreTimeUtilsProvider } from '@providers/utils/time';
|
||||
|
||||
/**
|
||||
* Blocked sync error.
|
||||
*/
|
||||
export class CoreSyncBlockedError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
|
||||
// Set the prototype explicitly, otherwise instanceof won't work as expected.
|
||||
Object.setPrototypeOf(this, CoreSyncBlockedError.prototype);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Base class to create sync providers. It provides some common functions.
|
||||
*/
|
||||
|
@ -52,6 +64,26 @@ export class CoreSyncBaseProvider {
|
|||
this.component = component;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an offline data deleted warning to a list of warnings.
|
||||
*
|
||||
* @param warnings List of warnings.
|
||||
* @param component Component.
|
||||
* @param name Instance name.
|
||||
* @param error Specific error message.
|
||||
*/
|
||||
protected addOfflineDataDeletedWarning(warnings: string[], component: string, name: string, error: string): void {
|
||||
const warning = this.translate.instant('core.warningofflinedatadeleted', {
|
||||
component: component,
|
||||
name: name,
|
||||
error: error,
|
||||
});
|
||||
|
||||
if (warnings.indexOf(warning) == -1) {
|
||||
warnings.push(warning);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an ongoing sync to the syncPromises list. On finish the promise will be removed.
|
||||
*
|
||||
|
@ -60,7 +92,7 @@ export class CoreSyncBaseProvider {
|
|||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return The sync promise.
|
||||
*/
|
||||
addOngoingSync(id: string | number, promise: Promise<any>, siteId?: string): Promise<any> {
|
||||
addOngoingSync<T>(id: string | number, promise: Promise<T>, siteId?: string): Promise<T> {
|
||||
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||
|
||||
const uniqueId = this.getUniqueSyncId(id);
|
||||
|
|
|
@ -124,6 +124,33 @@ export class CoreEventsProvider {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen for several events. To stop listening to the events:
|
||||
* let observer = eventsProvider.onMultiple(['something', 'another'], myCallBack);
|
||||
* ...
|
||||
* observer.off();
|
||||
*
|
||||
* @param eventNames Names of the events to listen to.
|
||||
* @param callBack Function to call when any of the events is triggered.
|
||||
* @param siteId Site where to trigger the event. Undefined won't check the site.
|
||||
* @return Observer to stop listening.
|
||||
*/
|
||||
onMultiple(eventNames: string[], callBack: (value: any) => void, siteId?: string): CoreEventObserver {
|
||||
|
||||
const observers = eventNames.map((name) => {
|
||||
return this.on(name, callBack, siteId);
|
||||
});
|
||||
|
||||
// Create and return a CoreEventObserver.
|
||||
return {
|
||||
off: (): void => {
|
||||
observers.forEach((observer) => {
|
||||
observer.off();
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers an event, notifying all the observers.
|
||||
*
|
||||
|
|
Loading…
Reference in New Issue