diff --git a/scripts/langindex.json b/scripts/langindex.json index 2b2ebb48f95..620d2c29978 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -430,6 +430,9 @@ "addon.mod_assign.numwords": "moodle", "addon.mod_assign.outof": "assign", "addon.mod_assign.overdue": "assign", + "addon.mod_assign.removesubmission": "assign", + "addon.mod_assign.removesubmissionconfirm": "assign", + "addon.mod_assign.removesubmissionconfirmwithtimelimit": "assign", "addon.mod_assign.submission": "assign", "addon.mod_assign.submissioneditable": "assign", "addon.mod_assign.submissionempty": "assign", diff --git a/src/addons/mod/assign/components/index/index.ts b/src/addons/mod/assign/components/index/index.ts index e343bbaff73..eb31d7487f4 100644 --- a/src/addons/mod/assign/components/index/index.ts +++ b/src/addons/mod/assign/components/index/index.ts @@ -47,6 +47,7 @@ import { ADDON_MOD_ASSIGN_GRADED_EVENT, ADDON_MOD_ASSIGN_PAGE_NAME, ADDON_MOD_ASSIGN_STARTED_EVENT, + ADDON_MOD_ASSIGN_SUBMISSION_REMOVED_EVENT, ADDON_MOD_ASSIGN_SUBMISSION_SAVED_EVENT, ADDON_MOD_ASSIGN_SUBMITTED_FOR_GRADING_EVENT, ADDON_MOD_ASSIGN_WARN_GROUPS_OPTIONAL, @@ -126,6 +127,17 @@ export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityCo this.siteId, ); + this.savedObserver = CoreEvents.on( + ADDON_MOD_ASSIGN_SUBMISSION_REMOVED_EVENT, + (data) => { + if (this.assign && data.assignmentId == this.assign.id && data.userId == this.currentUserId) { + // Assignment submission removed, refresh data. + this.showLoadingAndRefresh(true, false); + } + }, + this.siteId, + ); + this.submittedObserver = CoreEvents.on( ADDON_MOD_ASSIGN_SUBMITTED_FOR_GRADING_EVENT, (data) => { diff --git a/src/addons/mod/assign/components/submission/addon-mod-assign-submission.html b/src/addons/mod/assign/components/submission/addon-mod-assign-submission.html index 0ee574a3fb2..ad7abb28772 100644 --- a/src/addons/mod/assign/components/submission/addon-mod-assign-submission.html +++ b/src/addons/mod/assign/components/submission/addon-mod-assign-submission.html @@ -167,13 +167,20 @@

{{ 'addon.mod_assign.userswhoneedtosubmit' | translate: {$a: ''} }}

- - - {{ 'addon.mod_assign.editsubmission' | translate }} - + +
+ + {{ 'addon.mod_assign.editsubmission' | translate }} + + + {{ 'addon.mod_assign.removesubmission' | translate }} + +
- + {{ 'addon.mod_assign.addsubmission' | translate }} @@ -182,7 +189,7 @@

{{ 'addon.mod_assign.userswhoneedtosubmit' | translate: {$a: ''} }}

- + {{ 'addon.mod_assign.addnewattemptfromprevious' | translate }} @@ -191,12 +198,18 @@

{{ 'addon.mod_assign.userswhoneedtosubmit' | translate: {$a: ''} }}

{{ 'addon.mod_assign.addnewattempt' | translate }}
- - - {{ 'addon.mod_assign.editsubmission' | translate }} - + +
+ + {{ 'addon.mod_assign.editsubmission' | translate }} + + + {{ 'addon.mod_assign.removesubmission' | translate }} + +
diff --git a/src/addons/mod/assign/components/submission/submission.ts b/src/addons/mod/assign/components/submission/submission.ts index f5fdd8df273..1341860e885 100644 --- a/src/addons/mod/assign/components/submission/submission.ts +++ b/src/addons/mod/assign/components/submission/submission.ts @@ -64,6 +64,7 @@ import { ADDON_MOD_ASSIGN_GRADED_EVENT, ADDON_MOD_ASSIGN_MANUAL_SYNCED, ADDON_MOD_ASSIGN_PAGE_NAME, + ADDON_MOD_ASSIGN_SUBMISSION_REMOVED_EVENT, ADDON_MOD_ASSIGN_SUBMITTED_FOR_GRADING_EVENT, ADDON_MOD_ASSIGN_UNLIMITED_ATTEMPTS, } from '../../constants'; @@ -96,8 +97,9 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can isSubmittedForGrading = false; // Whether the submission has been submitted for grading. acceptStatement = false; // Statement accepted (for grading). feedback?: AddonModAssignSubmissionFeedbackFormatted; // The feedback. - hasOffline = false; // Whether there is offline data. + editedOffline = false; // Whether the submission was added or edited in offline. submittedOffline = false; // Whether it was submitted in offline. + removedOffline = false; // Whether the submission was removed in offline. fromDate?: string; // Readable date when the assign started accepting submissions. currentAttempt = 0; // The current attempt number. maxAttemptsText: string; // The text for maximum attempts. @@ -108,6 +110,7 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can membersToSubmitBlind: number[] = []; // Team members that need to submit the assignment (blindmarking). canSubmit = false; // Whether the user can submit for grading. canEdit = false; // Whether the user can edit the submission. + isRemoveAvailable = false; // Whether WS to remove submission is available. submissionStatement?: string; // The submission statement. showErrorStatementEdit = false; // Whether to show an error in edit due to submission statement. showErrorStatementSubmit = false; // Whether to show an error in submit due to submission statement. @@ -406,6 +409,47 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can ); } + /** + * Remove submisson. + */ + async remove(): Promise { + if (!this.assign || !this.userSubmission) { + return; + } + const message = this.assign?.timelimit ? + 'addon.mod_assign.removesubmissionconfirmwithtimelimit' : + 'addon.mod_assign.removesubmissionconfirm'; + try { + await CoreDomUtils.showDeleteConfirm(message); + } catch { + return; + } + + const modal = await CoreLoadings.show('core.sending', true); + + try { + const sent = await AddonModAssign.removeSubmission(this.assign, this.userSubmission); + + if (sent) { + CoreEvents.trigger(CoreEvents.ACTIVITY_DATA_SENT, { module: 'assign' }); + } + + CoreEvents.trigger( + ADDON_MOD_ASSIGN_SUBMISSION_REMOVED_EVENT, + { + assignmentId: this.assign.id, + submissionId: this.userSubmission.id, + userId: this.currentUserId, + }, + CoreSites.getCurrentSiteId(), + ); + } catch (error) { + CoreDomUtils.showErrorModalDefault(error, 'Error removing submission.'); + } finally { + modal.dismiss(); + } + } + /** * Check if there's data to save (grade). * @@ -633,13 +677,14 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can try { const submission = await AddonModAssignOffline.getSubmission(this.assign.id, this.submitId); - this.hasOffline = submission && submission.plugindata && Object.keys(submission.plugindata).length > 0; - - this.submittedOffline = !!submission?.submitted; + this.removedOffline = submission && Object.keys(submission.plugindata).length == 0; + this.editedOffline = submission && !this.removedOffline; + this.submittedOffline = !!submission?.submitted && !this.removedOffline; } catch (error) { // No offline data found. - this.hasOffline = false; + this.editedOffline = false; this.submittedOffline = false; + this.removedOffline = false; } } @@ -821,14 +866,14 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can return; } - if (this.hasOffline || this.submittedOffline) { - // Offline data. + if (this.editedOffline || this.submittedOffline) { + // Added, edited or submitted offline. this.statusTranslated = Translate.instant('core.notsent'); this.statusColor = CoreIonicColorNames.WARNING; } else if (!this.assign.teamsubmission) { // Single submission. - if (this.userSubmission && this.userSubmission.status != this.statusNew) { + if (this.userSubmission && this.userSubmission.status != this.statusNew && !this.removedOffline) { this.statusTranslated = Translate.instant('addon.mod_assign.submissionstatus_' + this.userSubmission.status); this.statusColor = AddonModAssign.getSubmissionStatusColor(this.userSubmission.status); } else { @@ -844,10 +889,10 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can } else { // Team submission. - if (!status.lastattempt?.submissiongroup && this.assign.preventsubmissionnotingroup) { + if (!status.lastattempt?.submissiongroup && this.assign.preventsubmissionnotingroup && !this.removedOffline) { this.statusTranslated = Translate.instant('addon.mod_assign.nosubmission'); this.statusColor = AddonModAssign.getSubmissionStatusColor(AddonModAssignSubmissionStatusValues.NO_SUBMISSION); - } else if (this.userSubmission && this.userSubmission.status != this.statusNew) { + } else if (this.userSubmission && this.userSubmission.status != this.statusNew && !this.removedOffline) { this.statusTranslated = Translate.instant('addon.mod_assign.submissionstatus_' + this.userSubmission.status); this.statusColor = AddonModAssign.getSubmissionStatusColor(this.userSubmission.status); } else { @@ -907,7 +952,7 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can this.courseId, acceptStatement, this.userSubmission.timemodified, - this.hasOffline, + this.editedOffline, ); // Submitted, trigger event. @@ -1142,11 +1187,12 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can this.assign.requiresubmissionstatement = 0; } - this.canSubmit = !this.isSubmittedForGrading && !this.submittedOffline && (lastAttempt.cansubmit || - (this.hasOffline && AddonModAssign.canSubmitOffline(this.assign, submissionStatus))); + this.canSubmit = !this.isSubmittedForGrading && !this.submittedOffline && !this.removedOffline && + (lastAttempt.cansubmit || (this.editedOffline && AddonModAssign.canSubmitOffline(this.assign, submissionStatus))); this.canEdit = !this.isSubmittedForGrading && lastAttempt.canedit && (!this.submittedOffline || !this.assign.submissiondrafts); + this.isRemoveAvailable = AddonModAssign.isRemoveSubmissionAvailable(); // Get submission statement if needed. if (this.assign.requiresubmissionstatement && this.assign.submissiondrafts && this.submitId == this.currentUserId) { diff --git a/src/addons/mod/assign/constants.ts b/src/addons/mod/assign/constants.ts index 52797e8540c..914a1a99805 100644 --- a/src/addons/mod/assign/constants.ts +++ b/src/addons/mod/assign/constants.ts @@ -25,6 +25,7 @@ export const ADDON_MOD_ASSIGN_WARN_GROUPS_OPTIONAL = 'warnoptional'; // Events. export const ADDON_MOD_ASSIGN_SUBMISSION_SAVED_EVENT = 'addon_mod_assign_submission_saved'; +export const ADDON_MOD_ASSIGN_SUBMISSION_REMOVED_EVENT = 'addon_mod_assign_submission_removed'; export const ADDON_MOD_ASSIGN_SUBMITTED_FOR_GRADING_EVENT = 'addon_mod_assign_submitted_for_grading'; export const ADDON_MOD_ASSIGN_GRADED_EVENT = 'addon_mod_assign_graded'; export const ADDON_MOD_ASSIGN_STARTED_EVENT = 'addon_mod_assign_started'; diff --git a/src/addons/mod/assign/lang.json b/src/addons/mod/assign/lang.json index fa94b86fc3d..e1e2cf0aa65 100644 --- a/src/addons/mod/assign/lang.json +++ b/src/addons/mod/assign/lang.json @@ -82,6 +82,9 @@ "numwords": "{{$a}} words", "outof": "{{$a.current}} out of {{$a.total}}", "overdue": "Assignment is overdue by: {{$a}}", + "removesubmission": "Remove submission", + "removesubmissionconfirm": "Are you sure you want to remove your submission?", + "removesubmissionconfirmwithtimelimit": "Are you sure you want to remove your submission? Please note that this will not reset your time limit.", "submission": "Submission", "submissioneditable": "Student can edit this submission", "submissionempty": "Nothing was submitted", diff --git a/src/addons/mod/assign/pages/edit/edit.ts b/src/addons/mod/assign/pages/edit/edit.ts index 5949a744687..6b6154c4378 100644 --- a/src/addons/mod/assign/pages/edit/edit.ts +++ b/src/addons/mod/assign/pages/edit/edit.ts @@ -231,15 +231,8 @@ export class AddonModAssignEditPage implements OnInit, OnDestroy, CanLeave { this.timeLimitEndTime = 0; } - try { - // Check if there's any offline data for this submission. - const offlineData = await AddonModAssignOffline.getSubmission(this.assign.id, this.userId); - - this.hasOffline = offlineData?.plugindata && Object.keys(offlineData.plugindata).length > 0; - } catch { - // No offline data found. - this.hasOffline = false; - } + // Check if there's any offline data for this submission. + this.hasOffline = await CoreUtils.promiseWorks(AddonModAssignOffline.getSubmission(this.assign.id, this.userId)); CoreAnalytics.logEvent({ type: CoreAnalyticsEventType.VIEW_ITEM, diff --git a/src/addons/mod/assign/services/assign-helper.ts b/src/addons/mod/assign/services/assign-helper.ts index 8c5d55b3531..a816ecab6de 100644 --- a/src/addons/mod/assign/services/assign-helper.ts +++ b/src/addons/mod/assign/services/assign-helper.ts @@ -81,6 +81,12 @@ export class AddonModAssignHelperProvider { return true; } + if (await CoreUtils.promiseWorks(AddonModAssignOffline.getSubmission(assign.id, submission.userid))) { + // Submission was saved or deleted offline, allow editing it or creating a new one. + return true; + } + + // Submission was created online, check if plugins allow editing it. let canEdit = true; const promises = submission.plugins @@ -492,7 +498,7 @@ export class AddonModAssignHelperProvider { submission.manyGroups = !!participant.groups && participant.groups.length > 1; submission.noGroups = !!participant.groups && participant.groups.length == 0; if (participant.groupname) { - submission.groupid = participant.groupid; + submission.groupid = participant.groupid ?? 0; submission.groupname = participant.groupname; } @@ -749,18 +755,15 @@ export const AddonModAssignHelper = makeSingleton(AddonModAssignHelperProvider); /** * Assign submission with some calculated data. */ -export type AddonModAssignSubmissionFormatted = - Omit & { - userid?: number; // Student id. - groupid?: number; // Group id. - blindid?: number; // Calculated in the app. Blindid of the user that did the submission. - submitid?: number; // Calculated in the app. Userid or blindid of the user that did the submission. - userfullname?: string; // Calculated in the app. Full name of the user that did the submission. - userprofileimageurl?: string; // Calculated in the app. Avatar of the user that did the submission. - manyGroups?: boolean; // Calculated in the app. Whether the user belongs to more than 1 group. - noGroups?: boolean; // Calculated in the app. Whether the user doesn't belong to any group. - groupname?: string; // Calculated in the app. Name of the group the submission belongs to. - }; +export interface AddonModAssignSubmissionFormatted extends AddonModAssignSubmission { + blindid?: number; // Calculated in the app. Blindid of the user that did the submission. + submitid?: number; // Calculated in the app. Userid or blindid of the user that did the submission. + userfullname?: string; // Calculated in the app. Full name of the user that did the submission. + userprofileimageurl?: string; // Calculated in the app. Avatar of the user that did the submission. + manyGroups?: boolean; // Calculated in the app. Whether the user belongs to more than 1 group. + noGroups?: boolean; // Calculated in the app. Whether the user doesn't belong to any group. + groupname?: string; // Calculated in the app. Name of the group the submission belongs to. +} /** * Assignment plugin config. diff --git a/src/addons/mod/assign/services/assign-sync.ts b/src/addons/mod/assign/services/assign-sync.ts index 19137968e20..063e3f1cdb3 100644 --- a/src/addons/mod/assign/services/assign-sync.ts +++ b/src/addons/mod/assign/services/assign-sync.ts @@ -331,27 +331,29 @@ export class AddonModAssignSyncProvider extends CoreCourseActivitySyncBaseProvid } try { - if (submission?.plugins) { - // Prepare plugins data. - await Promise.all(submission.plugins.map((plugin) => - AddonModAssignSubmissionDelegate.preparePluginSyncData( - assign, - submission, - plugin, - offlineData, - pluginData, - siteId, - ))); - } + if (Object.keys(offlineData.plugindata).length == 0) { + await AddonModAssign.removeSubmissionOnline(assign.id, offlineData.userid, siteId); + } else { + if (submission?.plugins) { + // Prepare plugins data. + await Promise.all(submission.plugins.map((plugin) => + AddonModAssignSubmissionDelegate.preparePluginSyncData( + assign, + submission, + plugin, + offlineData, + pluginData, + siteId, + ))); + } - // Now save the submission. - if (Object.keys(pluginData).length > 0) { + // Now save the submission. await AddonModAssign.saveSubmissionOnline(assign.id, pluginData, siteId); - } - if (assign.submissiondrafts && offlineData.submitted) { - // The user submitted the assign manually. Submit it for grading. - await AddonModAssign.submitForGradingOnline(assign.id, !!offlineData.submissionstatement, siteId); + if (assign.submissiondrafts && offlineData.submitted) { + // The user submitted the assign manually. Submit it for grading. + await AddonModAssign.submitForGradingOnline(assign.id, !!offlineData.submissionstatement, siteId); + } } // Submission data sent, update cached data. No need to block the user for this. diff --git a/src/addons/mod/assign/services/assign.ts b/src/addons/mod/assign/services/assign.ts index 976ca8abed3..30c55b18303 100644 --- a/src/addons/mod/assign/services/assign.ts +++ b/src/addons/mod/assign/services/assign.ts @@ -42,6 +42,7 @@ import { ADDON_MOD_ASSIGN_GRADED_EVENT, ADDON_MOD_ASSIGN_MANUAL_SYNCED, ADDON_MOD_ASSIGN_STARTED_EVENT, + ADDON_MOD_ASSIGN_SUBMISSION_REMOVED_EVENT, ADDON_MOD_ASSIGN_SUBMISSION_SAVED_EVENT, ADDON_MOD_ASSIGN_SUBMITTED_FOR_GRADING_EVENT, } from '../constants'; @@ -55,6 +56,7 @@ declare module '@singletons/events' { */ export interface CoreEventsData { [ADDON_MOD_ASSIGN_SUBMISSION_SAVED_EVENT]: AddonModAssignSubmissionSavedEventData; + [ADDON_MOD_ASSIGN_SUBMISSION_REMOVED_EVENT]: AddonModAssignSubmissionRemovedEventData; [ADDON_MOD_ASSIGN_SUBMITTED_FOR_GRADING_EVENT]: AddonModAssignSubmittedForGradingEventData; [ADDON_MOD_ASSIGN_GRADED_EVENT]: AddonModAssignGradedEventData; [ADDON_MOD_ASSIGN_STARTED_EVENT]: AddonModAssignStartedEventData; @@ -1314,6 +1316,123 @@ export class AddonModAssignProvider { } } + /** + * Remove the assignment submission of a user. + * + * @param assign Assign. + * @param submission Last online submission. + * @param siteId Site ID. If not defined, current site. + * @returns Promise resolved with true if sent to server, resolved with false if stored in offline. + * @since 4.5 + */ + async removeSubmission( + assign: AddonModAssignAssign, + submission: AddonModAssignSubmission, + siteId?: string, + ): Promise { + siteId = siteId || CoreSites.getCurrentSiteId(); + + // Function to store the submission to be synchronized later. + const storeOffline = async (): Promise => { + if (submission.status === AddonModAssignSubmissionStatusValues.NEW || + submission.status == AddonModAssignSubmissionStatusValues.REOPENED) { + // The submission was created offline and not synced, just delete the offline submission. + await AddonModAssignOffline.deleteSubmission(assign.id, submission.userid, siteId); + } else { + await AddonModAssignOffline.saveSubmission( + assign.id, + assign.course, + {}, + submission.timemodified, + !!assign.submissiondrafts, + submission.userid, + siteId, + ); + } + + return false; + }; + + if (!CoreNetwork.isOnline()) { + // App is offline, store the action. + return storeOffline(); + } + + try { + // If there's an offline submission, discard it first. + const offlineData = await AddonModAssignOffline.getSubmission(assign.id, submission.userid, siteId); + + if (offlineData) { + if (submission.plugins) { + // Delete all plugin data. + await Promise.all(submission.plugins.map((plugin) => + AddonModAssignSubmissionDelegate.deletePluginOfflineData( + assign, + submission, + plugin, + offlineData, + siteId, + ))); + } + + await AddonModAssignOffline.deleteSubmission(assign.id, submission.userid, siteId); + } + + await this.removeSubmissionOnline(assign.id, submission.userid, siteId); + + return true; + } catch (error) { + if (error && !CoreUtils.isWebServiceError(error)) { + // Couldn't connect to server, store in offline. + return storeOffline(); + } else { + // The WebService has thrown an error or offline not supported, reject. + throw error; + } + } + } + + /** + * Remove the assignment submission of a user. + * + * @param assignId Assign ID. + * @param userId User ID. If not defined, current user. + * @param siteId Site ID. If not defined, current site. + * @returns Promise resolved when submitted, rejected otherwise. + * @since 4.5 + */ + async removeSubmissionOnline(assignId: number, userId?: number, siteId?: string): Promise { + const site = await CoreSites.getSite(siteId); + userId = userId || site.getUserId(); + + const params: AddonModAssignRemoveSubmissionWSParams = { + assignid: assignId, + userid: userId, + }; + const result = await site.write('mod_assign_remove_submission', params); + + if (!result.status) { + if (result.warnings?.length) { + throw new CoreWSError(result.warnings[0]); + } else { + throw new CoreError('Error removing assignment submission.'); + } + } + } + + /** + * Returns whether or not remove submission WS available or not. + * + * @param site Site. If not defined, current site. + * @returns If WS is available. + * @since 4.5 + */ + isRemoveSubmissionAvailable(site?: CoreSite): boolean { + site = site ?? CoreSites.getRequiredCurrentSite(); + + return site.wsAvailable('mod_assign_remove_submission'); + } + } export const AddonModAssign = makeSingleton(AddonModAssignProvider); @@ -1755,6 +1874,22 @@ type AddonModAssignStartSubmissionWSParams = { assignid: number; // Assignment instance id. }; +/** + * Params of mod_assign_remove_submission WS. + */ +type AddonModAssignRemoveSubmissionWSParams = { + userid: number; // User id. + assignid: number; // Assignment instance id. +}; + +/** + * Data returned by mod_assign_remove_submission WS. + */ +export type AddonModAssignRemoveSubmissionWSResponse = { + status: boolean; + warnings?: CoreWSExternalWarning[]; +}; + /** * Data returned by mod_assign_start_submission WS. * @@ -1784,6 +1919,11 @@ export type AddonModAssignSubmittedForGradingEventData = { */ export type AddonModAssignSubmissionSavedEventData = AddonModAssignSubmittedForGradingEventData; +/** + * Data sent by SUBMISSION_REMOVED_EVENT event. + */ +export type AddonModAssignSubmissionRemovedEventData = AddonModAssignSubmittedForGradingEventData; + /** * Data sent by GRADED_EVENT event. */ diff --git a/src/addons/mod/assign/submission/file/component/file.ts b/src/addons/mod/assign/submission/file/component/file.ts index 588c7e9ee96..e142c842f5c 100644 --- a/src/addons/mod/assign/submission/file/component/file.ts +++ b/src/addons/mod/assign/submission/file/component/file.ts @@ -54,28 +54,31 @@ export class AddonModAssignSubmissionFileComponent extends AddonModAssignSubmiss : undefined; // Get the offline data. - const filesData = await CoreUtils.ignoreErrors( + const offlineData = await CoreUtils.ignoreErrors( AddonModAssignOffline.getSubmission(this.assign.id), undefined, ); try { - if (filesData && filesData.plugindata && filesData.plugindata.files_filemanager) { - const offlineDataFiles = filesData.plugindata.files_filemanager; - // It has offline data. - let offlineFiles: FileEntry[] = []; - if (offlineDataFiles.offline) { - offlineFiles = await CoreUtils.ignoreErrors( - AddonModAssignHelper.getStoredSubmissionFiles( - this.assign.id, - AddonModAssignSubmissionFileHandlerService.FOLDER_NAME, - ), - [], - ); - } + if (offlineData) { + // Offline submission, get files if submission is not removed. + if (offlineData.plugindata.files_filemanager) { + const offlineDataFiles = offlineData.plugindata.files_filemanager; + // It has offline data. + let offlineFiles: FileEntry[] = []; + if (offlineDataFiles.offline) { + offlineFiles = await CoreUtils.ignoreErrors( + AddonModAssignHelper.getStoredSubmissionFiles( + this.assign.id, + AddonModAssignSubmissionFileHandlerService.FOLDER_NAME, + ), + [], + ); + } - this.files = offlineDataFiles.online || []; - this.files = this.files.concat(offlineFiles); + this.files = offlineDataFiles.online || []; + this.files = this.files.concat(offlineFiles); + } } else { // No offline data, get the online files. this.files = AddonModAssign.getSubmissionPluginAttachments(this.plugin); diff --git a/src/addons/mod/assign/submission/onlinetext/component/onlinetext.ts b/src/addons/mod/assign/submission/onlinetext/component/onlinetext.ts index 47760672d7f..04447b4d330 100644 --- a/src/addons/mod/assign/submission/onlinetext/component/onlinetext.ts +++ b/src/addons/mod/assign/submission/onlinetext/component/onlinetext.ts @@ -69,8 +69,11 @@ export class AddonModAssignSubmissionOnlineTextComponent extends AddonModAssignS this.wordLimit = parseInt(this.configs?.wordlimit || '0'); try { - if (offlineData && offlineData.plugindata && offlineData.plugindata.onlinetext_editor) { - this.text = (offlineData.plugindata).onlinetext_editor.text; + if (offlineData && offlineData.plugindata) { + // Offline submission, get text if submission is not removed. + if (offlineData.plugindata.onlinetext_editor) { + this.text = (offlineData.plugindata).onlinetext_editor.text; + } } else { // No offline data found, return online text. this.text = AddonModAssign.getSubmissionPluginText(this.plugin); diff --git a/src/addons/mod/assign/tests/behat/basic_usage.feature b/src/addons/mod/assign/tests/behat/basic_usage.feature index 94bce17794b..b0fd2ba4c24 100755 --- a/src/addons/mod/assign/tests/behat/basic_usage.feature +++ b/src/addons/mod/assign/tests/behat/basic_usage.feature @@ -120,6 +120,17 @@ Feature: Test basic usage of assignment activity in app Then I should find "Online text submissions" in the app And I should find "Submission test 2nd attempt" in the app + @lms_from4.5 + Scenario: Remove submission (online text) + Given I entered the assign activity "assignment1" on course "Course 1" as "student1" in the app + And I press "Add submission" in the app + And I set the field "Online text submissions" to "Submission test" in the app + And I press "Save" in the app + + When I press "Remove submission" in the app + And I press "DELETE" in the app + Then I should find "No attempt" in the app + Scenario: Add submission offline (online text) & Submit for grading offline & Sync submissions Given I entered the assign activity "assignment1" on course "Course 1" as "student1" in the app When I press "Add submission" in the app @@ -164,3 +175,61 @@ Feature: Test basic usage of assignment activity in app Then I should find "Submitted for grading" in the app And I should find "Submission test edited offline" in the app But I should not find "This Assignment has offline data to be synchronised." in the app + + @lms_from4.5 + Scenario: Remove submission offline and syncrhonize it + Given I entered the assign activity "assignment1" on course "Course 1" as "student1" in the app + And I press "Add submission" in the app + And I set the field "Online text submissions" to "Submission test" in the app + And I press "Save" in the app + Then I should find "Draft (not submitted)" in the app + + # Remove submission added online. + When I switch network connection to offline + And I press "Remove submission" in the app + And I press "DELETE" in the app + Then I should find "No attempt" in the app + And I should find "This Assignment has offline data to be synchronised." in the app + + # Synchronize submission removal. + When I switch network connection to wifi + And I press the back button in the app + And I press "assignment1" in the app + Then I should find "No attempt" in the app + But I should not find "This Assignment has offline data to be synchronised." in the app + + # Remove submission added offline + Given I press "Add submission" in the app + And I set the field "Online text submissions" to "Submission test offline" in the app + And I switch network connection to offline + And I press "Save" in the app + + When I press "Remove submission" in the app + And I press "DELETE" in the app + Then I should find "No attempt" in the app + But I should not find "This Assignment has offline data to be synchronised." in the app + + @lms_from4.5 + Scenario: Add submission offline after removing a submission offline + Given I entered the assign activity "assignment1" on course "Course 1" as "student1" in the app + When I press "Add submission" in the app + And I set the field "Online text submissions" to "Submission test online" in the app + And I press "Save" in the app + And I switch network connection to offline + And I press "Remove submission" in the app + And I press "DELETE" in the app + Then I should find "This Assignment has offline data to be synchronised." in the app + And I should find "No attempt" in the app + + When I press "Add submission" in the app + And I set the field "Online text submissions" to "Submission test offline" in the app + And I press "Save" in the app + And I press "OK" in the app + Then I should find "This Assignment has offline data to be synchronised." in the app + And I should find "Submission test offline" in the app + + When I switch network connection to wifi + And I go back in the app + And I press "assignment1" in the app + Then I should find "Submission test offline" in the app + But I should not find "This Assignment has offline data to be synchronised." in the app