MOBILE-2334 assign: Implement assign provider and offline

main
Dani Palou 2018-04-10 09:23:32 +02:00
parent e9f7bd0bc5
commit 941295cccd
5 changed files with 1760 additions and 2 deletions

View File

@ -13,6 +13,8 @@
// limitations under the License.
import { NgModule } from '@angular/core';
import { AddonModAssignProvider } from './providers/assign';
import { AddonModAssignOfflineProvider } from './providers/assign-offline';
import { AddonModAssignFeedbackDelegate } from './providers/feedback-delegate';
import { AddonModAssignSubmissionDelegate } from './providers/submission-delegate';
import { AddonModAssignDefaultFeedbackHandler } from './providers/default-feedback-handler';
@ -22,6 +24,8 @@ import { AddonModAssignDefaultSubmissionHandler } from './providers/default-subm
declarations: [
],
providers: [
AddonModAssignProvider,
AddonModAssignOfflineProvider,
AddonModAssignFeedbackDelegate,
AddonModAssignSubmissionDelegate,
AddonModAssignDefaultFeedbackHandler,

View File

@ -0,0 +1,503 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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 { CoreFileProvider } from '@providers/file';
import { CoreLoggerProvider } from '@providers/logger';
import { CoreSitesProvider } from '@providers/sites';
import { CoreTextUtilsProvider } from '@providers/utils/text';
import { CoreTimeUtilsProvider } from '@providers/utils/time';
/**
* Service to handle offline assign.
*/
@Injectable()
export class AddonModAssignOfflineProvider {
protected logger;
// Variables for database.
protected SUBMISSIONS_TABLE = 'addon_mod_assign_submissions';
protected SUBMISSIONS_GRADES_TABLE = 'addon_mod_assign_submissions_grading';
protected tablesSchema = [
{
name: this.SUBMISSIONS_TABLE,
columns: [
{
name: 'assignId',
type: 'INTEGER'
},
{
name: 'courseId',
type: 'INTEGER'
},
{
name: 'userId',
type: 'INTEGER'
},
{
name: 'pluginData',
type: 'TEXT'
},
{
name: 'onlineTimemodified',
type: 'INTEGER'
},
{
name: 'timecreated',
type: 'INTEGER'
},
{
name: 'timemodified',
type: 'INTEGER'
},
{
name: 'submitted',
type: 'INTEGER'
},
{
name: 'submissionStatement',
type: 'INTEGER'
}
],
primaryKeys: ['assignId', 'userId']
},
{
name: this.SUBMISSIONS_GRADES_TABLE,
columns: [
{
name: 'assignId',
type: 'INTEGER'
},
{
name: 'courseId',
type: 'INTEGER'
},
{
name: 'userId',
type: 'INTEGER'
},
{
name: 'grade',
type: 'REAL'
},
{
name: 'attemptNumber',
type: 'INTEGER'
},
{
name: 'addAttempt',
type: 'INTEGER'
},
{
name: 'workflowState',
type: 'TEXT'
},
{
name: 'applyToAll',
type: 'INTEGER'
},
{
name: 'outcomes',
type: 'TEXT'
},
{
name: 'pluginData',
type: 'TEXT'
},
{
name: 'timemodified',
type: 'INTEGER'
}
],
primaryKeys: ['assignId', 'userId']
}
];
constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private textUtils: CoreTextUtilsProvider,
private fileProvider: CoreFileProvider, private timeUtils: CoreTimeUtilsProvider) {
this.logger = logger.getInstance('AddonModAssignOfflineProvider');
this.sitesProvider.createTablesFromSchema(this.tablesSchema);
}
/**
* Delete a submission.
*
* @param {number} assignId Assignment ID.
* @param {number} [userId] User ID. If not defined, site's current user.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved if deleted, rejected if failure.
*/
deleteSubmission(assignId: number, userId?: number, siteId?: string): Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => {
userId = userId || site.getUserId();
return site.getDb().deleteRecords(this.SUBMISSIONS_TABLE, {assignId, userId});
});
}
/**
* Delete a submission grade.
*
* @param {number} assignId Assignment ID.
* @param {number} [userId] User ID. If not defined, site's current user.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved if deleted, rejected if failure.
*/
deleteSubmissionGrade(assignId: number, userId?: number, siteId?: string): Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => {
userId = userId || site.getUserId();
return site.getDb().deleteRecords(this.SUBMISSIONS_GRADES_TABLE, {assignId, userId});
});
}
/**
* Get all the assignments ids that have something to be synced.
*
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<number[]>} Promise resolved with assignments id that have something to be synced.
*/
getAllAssigns(siteId?: string): Promise<number[]> {
const promises = [];
promises.push(this.getAllSubmissions(siteId));
promises.push(this.getAllSubmissionsGrade(siteId));
return Promise.all(promises).then((results) => {
// Flatten array.
results = [].concat.apply([], results);
// Get assign id.
results = results.map((object) => {
return object.assignId;
});
// Get unique values.
results = results.filter((id, pos) => {
return results.indexOf(id) == pos;
});
return results;
});
}
/**
* Get all the stored submissions from all the assignments.
*
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any[]} Promise resolved with submissions.
*/
protected getAllSubmissions(siteId?: string): Promise<any[]> {
return this.sitesProvider.getSiteDb(siteId).then((db) => {
return db.getAllRecords(this.SUBMISSIONS_TABLE);
}).then((submissions) => {
// Parse the plugin data.
submissions.forEach((submission) => {
submission.pluginData = this.textUtils.parseJSON(submission.pluginData, {});
});
return submissions;
});
}
/**
* Get all the stored submissions grades from all the assignments.
*
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any[]>} Promise resolved with submissions grades.
*/
protected getAllSubmissionsGrade(siteId?: string): Promise<any[]> {
return this.sitesProvider.getSiteDb(siteId).then((db) => {
return db.getAllRecords(this.SUBMISSIONS_GRADES_TABLE);
}).then((submissions) => {
// Parse the plugin data and outcomes.
submissions.forEach((submission) => {
submission.outcomes = this.textUtils.parseJSON(submission.outcomes, {});
submission.pluginData = this.textUtils.parseJSON(submission.pluginData, {});
});
return submissions;
});
}
/**
* Get all the stored submissions for a certain assignment.
*
* @param {number} assignId Assignment ID.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any[]>} Promise resolved with submissions.
*/
getAssignSubmissions(assignId: number, siteId?: string): Promise<any[]> {
return this.sitesProvider.getSiteDb(siteId).then((db) => {
return db.getRecords(this.SUBMISSIONS_TABLE, {assignId});
}).then((submissions) => {
// Parse the plugin data.
submissions.forEach((submission) => {
submission.pluginData = this.textUtils.parseJSON(submission.pluginData, {});
});
return submissions;
});
}
/**
* Get all the stored submissions grades for a certain assignment.
*
* @param {number} assignId Assignment ID.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any[]>} Promise resolved with submissions grades.
*/
getAssignSubmissionsGrade(assignId: number, siteId?: string): Promise<any[]> {
return this.sitesProvider.getSiteDb(siteId).then((db) => {
return db.getRecords(this.SUBMISSIONS_GRADES_TABLE, {assignId});
}).then((submissions) => {
// Parse the plugin data and outcomes.
submissions.forEach((submission) => {
submission.outcomes = this.textUtils.parseJSON(submission.outcomes, {});
submission.pluginData = this.textUtils.parseJSON(submission.pluginData, {});
});
return submissions;
});
}
/**
* Get a stored submission.
*
* @param {number} assignId Assignment ID.
* @param {number} [userId] User ID. If not defined, site's current user.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved with submission.
*/
getSubmission(assignId: number, userId?: number, siteId?: string): Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => {
userId = userId || site.getUserId();
return site.getDb().getRecord(this.SUBMISSIONS_TABLE, {assignId, userId});
}).then((submission) => {
// Parse the plugin data.
submission.pluginData = this.textUtils.parseJSON(submission.pluginData, {});
return submission;
});
}
/**
* Get the path to the folder where to store files for an offline submission.
*
* @param {number} assignId Assignment ID.
* @param {number} [userId] User ID. If not defined, site's current user.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<string>} Promise resolved with the path.
*/
getSubmissionFolder(assignId: number, userId?: number, siteId?: string): Promise<string> {
return this.sitesProvider.getSite(siteId).then((site) => {
userId = userId || site.getUserId();
const siteFolderPath = this.fileProvider.getSiteFolder(site.getId()),
submissionFolderPath = 'offlineassign/' + assignId + '/' + userId;
return this.textUtils.concatenatePaths(siteFolderPath, submissionFolderPath);
});
}
/**
* Get a stored submission grade.
* Submission grades are not identified using attempt number so it can retrieve the feedback for a previous attempt.
*
* @param {number} assignId Assignment ID.
* @param {number} [userId] User ID. If not defined, site's current user.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved with submission grade.
*/
getSubmissionGrade(assignId: number, userId?: number, siteId?: string): Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => {
userId = userId || site.getUserId();
return site.getDb().getRecord(this.SUBMISSIONS_GRADES_TABLE, {assignId, userId});
}).then((submission) => {
// Parse the plugin data and outcomes.
submission.outcomes = this.textUtils.parseJSON(submission.outcomes, {});
submission.pluginData = this.textUtils.parseJSON(submission.pluginData, {});
return submission;
});
}
/**
* Get the path to the folder where to store files for a certain plugin in an offline submission.
*
* @param {number} assignId Assignment ID.
* @param {string} pluginName Name of the plugin. Must be unique (both in submission and feedback plugins).
* @param {number} [userId] User ID. If not defined, site's current user.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<string>} Promise resolved with the path.
*/
getSubmissionPluginFolder(assignId: number, pluginName: string, userId?: number, siteId?: string): Promise<string> {
return this.getSubmissionFolder(assignId, userId, siteId).then((folderPath) => {
return this.textUtils.concatenatePaths(folderPath, pluginName);
});
}
/**
* Check if the assignment has something to be synced.
*
* @param {number} assignId Assignment ID.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<boolean>} Promise resolved with boolean: whether the assignment has something to be synced.
*/
hasAssignOfflineData(assignId: number, siteId?: string): Promise<boolean> {
const promises = [];
promises.push(this.getAssignSubmissions(assignId, siteId));
promises.push(this.getAssignSubmissionsGrade(assignId, siteId));
return Promise.all(promises).then((results) => {
for (let i = 0; i < results.length; i++) {
const result = results[i];
if (result && result.length) {
return true;
}
}
return false;
}).catch(() => {
// No offline data found.
return false;
});
}
/**
* Mark/Unmark a submission as being submitted.
*
* @param {number} assignId Assignment ID.
* @param {number} courseId Course ID the assign belongs to.
* @param {boolean} submitted True to mark as submitted, false to mark as not submitted.
* @param {boolean} acceptStatement True to accept the submission statement, false otherwise.
* @param {number} timemodified The time the submission was last modified in online.
* @param {number} [userId] User ID. If not defined, site's current user.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved if marked, rejected if failure.
*/
markSubmitted(assignId: number, courseId: number, submitted: boolean, acceptStatement: boolean, timemodified: number,
userId?: number, siteId?: string): Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => {
userId = userId || site.getUserId();
// Check if there's a submission stored.
return this.getSubmission(assignId, userId, site.getId()).catch(() => {
// No submission, create an empty one.
const now = this.timeUtils.timestamp();
return {
assignId: assignId,
courseId: courseId,
pluginData: '{}',
userId: userId,
onlineTimemodified: timemodified,
timecreated: now,
timemodified: now
};
}).then((submission) => {
// Mark the submission.
submission.submitted = !!submitted;
submission.submissionstatement = !!acceptStatement;
return site.getDb().insertRecord(this.SUBMISSIONS_TABLE, submission);
});
});
}
/**
* Save a submission to be sent later.
*
* @param {number} assignId Assignment ID.
* @param {number} courseId Course ID the assign belongs to.
* @param {any} pluginData Data to save.
* @param {number} timemodified The time the submission was last modified in online.
* @param {boolean} submitted True if submission has been submitted, false otherwise.
* @param {number} [userId] User ID. If not defined, site's current user.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved if stored, rejected if failure.
*/
saveSubmission(assignId: number, courseId: number, pluginData: any, timemodified: number, submitted: boolean, userId?: number,
siteId?: string): Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => {
userId = userId || site.getUserId();
const now = this.timeUtils.timestamp(),
entry = {
assignId: assignId,
courseId: courseId,
pluginData: pluginData ? JSON.stringify(pluginData) : '{}',
userId: userId,
submitted: !!submitted,
timecreated: now,
timemodified: now,
onlineTimemodified: timemodified
};
return site.getDb().insertRecord(this.SUBMISSIONS_TABLE, entry);
});
}
/**
* Save a grading to be sent later.
*
* @param {number} assignId Assign ID.
* @param {number} userId User ID.
* @param {number} courseId Course ID the assign belongs to.
* @param {number} grade Grade to submit.
* @param {number} attemptNumber Number of the attempt being graded.
* @param {boolean} addAttempt Admit the user to attempt again.
* @param {string} workflowState Next workflow State.
* @param {boolean} applyToAll If it's a team submission, whether the grade applies to all group members.
* @param {any} outcomes Object including all outcomes values. If empty, any of them will be sent.
* @param {any} pluginData Plugin data to save.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved if stored, rejected if failure.
*/
submitGradingForm(assignId: number, userId: number, courseId: number, grade: number, attemptNumber: number, addAttempt: boolean,
workflowState: string, applyToAll: boolean, outcomes: any, pluginData: any, siteId?: string): Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => {
const now = this.timeUtils.timestamp(),
entry = {
assignId: assignId,
userId: userId,
courseId: courseId,
grade: grade,
attemptNumber: attemptNumber,
addAttempt: !!addAttempt,
workflowState: workflowState,
applyToAll: !!applyToAll,
outcomes: outcomes ? JSON.stringify(outcomes) : '{}',
pluginData: pluginData ? JSON.stringify(pluginData) : '{}',
timemodified: now
};
return site.getDb().insertRecord(this.SUBMISSIONS_GRADES_TABLE, entry);
});
}
}

File diff suppressed because it is too large Load Diff

View File

@ -210,6 +210,19 @@ export class CoreGradesProvider {
});
}
/**
* Invalidates courses grade table and items WS calls for all users.
*
* @param {number} courseId ID of the course to get the grades from.
* @param {string} [siteId] Site ID (empty for current site).
* @return {Promise<any>} Promise resolved when the data is invalidated.
*/
invalidateAllCourseGradesData(courseId: number, siteId?: string): Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => {
return site.invalidateWsCacheForKeyStartingWith(this.getCourseGradesPrefixCacheKey(courseId));
});
}
/**
* Invalidates grade table data WS calls.
*
@ -247,7 +260,7 @@ export class CoreGradesProvider {
* @param {string} [siteId] Site id (empty for current site).
* @return {Promise<any>} Promise resolved when the data is invalidated.
*/
invalidateCourseGradesItemsData(courseId: number, userId: number, groupId: number, siteId?: string): Promise<any> {
invalidateCourseGradesItemsData(courseId: number, userId: number, groupId?: number, siteId?: string): Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => {
return site.invalidateWsCacheForKey(this.getCourseGradesItemsCacheKey(courseId, userId, groupId));
});

View File

@ -626,7 +626,7 @@ export class CoreUtilsProvider {
* @return {boolean} Whether the error was returned by the WebService.
*/
isWebServiceError(error: any): boolean {
return typeof error.errorcode == 'undefined';
return typeof error.errorcode == 'undefined' && typeof error.warningcode == 'undefined';
}
/**