Skip to content

Commit 588df2d

Browse files
committed
MOBILE-3893 assign: Add button to remove submissions
1 parent a6076d3 commit 588df2d

File tree

13 files changed

+389
-84
lines changed

13 files changed

+389
-84
lines changed

scripts/langindex.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,9 @@
430430
"addon.mod_assign.numwords": "moodle",
431431
"addon.mod_assign.outof": "assign",
432432
"addon.mod_assign.overdue": "assign",
433+
"addon.mod_assign.removesubmission": "assign",
434+
"addon.mod_assign.removesubmissionconfirm": "assign",
435+
"addon.mod_assign.removesubmissionconfirmwithtimelimit": "assign",
433436
"addon.mod_assign.submission": "assign",
434437
"addon.mod_assign.submissioneditable": "assign",
435438
"addon.mod_assign.submissionempty": "assign",

src/addons/mod/assign/components/index/index.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import {
4747
ADDON_MOD_ASSIGN_GRADED_EVENT,
4848
ADDON_MOD_ASSIGN_PAGE_NAME,
4949
ADDON_MOD_ASSIGN_STARTED_EVENT,
50+
ADDON_MOD_ASSIGN_SUBMISSION_REMOVED_EVENT,
5051
ADDON_MOD_ASSIGN_SUBMISSION_SAVED_EVENT,
5152
ADDON_MOD_ASSIGN_SUBMITTED_FOR_GRADING_EVENT,
5253
ADDON_MOD_ASSIGN_WARN_GROUPS_OPTIONAL,
@@ -126,6 +127,17 @@ export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityCo
126127
this.siteId,
127128
);
128129

130+
this.savedObserver = CoreEvents.on(
131+
ADDON_MOD_ASSIGN_SUBMISSION_REMOVED_EVENT,
132+
(data) => {
133+
if (this.assign && data.assignmentId == this.assign.id && data.userId == this.currentUserId) {
134+
// Assignment submission removed, refresh data.
135+
this.showLoadingAndRefresh(true, false);
136+
}
137+
},
138+
this.siteId,
139+
);
140+
129141
this.submittedObserver = CoreEvents.on(
130142
ADDON_MOD_ASSIGN_SUBMITTED_FOR_GRADING_EVENT,
131143
(data) => {

src/addons/mod/assign/components/submission/addon-mod-assign-submission.html

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -167,13 +167,20 @@ <h2>{{ 'addon.mod_assign.userswhoneedtosubmit' | translate: {$a: ''} }}</h2>
167167
<div class="list-item-limited-width" *ngIf="canEdit || canSubmit">
168168
<ng-container *ngIf="canEdit">
169169
<ng-container *ngIf=" !unsupportedEditPlugins.length && !showErrorStatementEdit">
170-
<!-- If has offline data, show edit. -->
171-
<ion-button expand="block" class="ion-text-wrap" *ngIf="hasOffline" (click)="goToEdit()">
172-
{{ 'addon.mod_assign.editsubmission' | translate }}
173-
</ion-button>
170+
<!-- If has offline data, show edit and remove. -->
171+
<div *ngIf="editedOffline" class="adaptable-buttons-row">
172+
<ion-button expand="block" class="ion-margin ion-text-wrap" (click)="goToEdit()">
173+
{{ 'addon.mod_assign.editsubmission' | translate }}
174+
</ion-button>
175+
<ion-button *ngIf="isRemoveAvailable" expand="block" class="ion-margin ion-text-wrap"
176+
(click)="remove()">
177+
{{ 'addon.mod_assign.removesubmission' | translate }}
178+
</ion-button>
179+
</div>
174180
<!-- If no submission or is new, show add submission. -->
175-
<ion-button expand="block" class="ion-text-wrap" (click)="goToEdit()" *ngIf="!hasOffline &&
176-
(!userSubmission || !userSubmission!.status || userSubmission!.status === statusNew)">
181+
<ion-button expand="block" class="ion-text-wrap" (click)="goToEdit()" *ngIf="!editedOffline &&
182+
(removedOffline || !userSubmission || !userSubmission!.status ||
183+
userSubmission!.status === statusNew)">
177184
<ng-container *ngIf="!assign?.timelimit || userSubmission?.timestarted">
178185
{{ 'addon.mod_assign.addsubmission' | translate }}
179186
</ng-container>
@@ -182,7 +189,7 @@ <h2>{{ 'addon.mod_assign.userswhoneedtosubmit' | translate: {$a: ''} }}</h2>
182189
</ng-container>
183190
</ion-button>
184191
<!-- If reopened, show addfromprevious and addnewattempt. -->
185-
<ng-container *ngIf="!hasOffline && userSubmission?.status === statusReopened">
192+
<ng-container *ngIf="!editedOffline && !removedOffline && userSubmission?.status === statusReopened">
186193
<ion-button *ngIf="!isPreviousAttemptEmpty" expand="block" class="ion-text-wrap"
187194
(click)="copyPrevious()">
188195
{{ 'addon.mod_assign.addnewattemptfromprevious' | translate }}
@@ -191,12 +198,18 @@ <h2>{{ 'addon.mod_assign.userswhoneedtosubmit' | translate: {$a: ''} }}</h2>
191198
{{ 'addon.mod_assign.addnewattempt' | translate }}
192199
</ion-button>
193200
</ng-container>
194-
<!-- Else show editsubmission. -->
195-
<ion-button expand="block" class="ion-text-wrap" *ngIf="!hasOffline && userSubmission &&
196-
userSubmission!.status && userSubmission!.status !== statusNew &&
197-
userSubmission!.status !== statusReopened" (click)="goToEdit()">
198-
{{ 'addon.mod_assign.editsubmission' | translate }}
199-
</ion-button>
201+
<!-- Else show editsubmission and removesubmission. -->
202+
<div *ngIf="!editedOffline && !removedOffline && userSubmission && userSubmission!.status
203+
&& userSubmission!.status !== statusNew && userSubmission!.status !== statusReopened"
204+
class="adaptable-buttons-row">
205+
<ion-button expand="block" class="ion-margin ion-text-wrap" (click)="goToEdit()">
206+
{{ 'addon.mod_assign.editsubmission' | translate }}
207+
</ion-button>
208+
<ion-button *ngIf="isRemoveAvailable" expand="block" class="ion-margin ion-text-wrap"
209+
(click)="remove()">
210+
{{ 'addon.mod_assign.removesubmission' | translate }}
211+
</ion-button>
212+
</div>
200213
</ng-container>
201214
<ion-item class="core-danger-item ion-text-wrap"
202215
*ngIf="(unsupportedEditPlugins.length && !showErrorStatementEdit)|| showErrorStatementEdit">

src/addons/mod/assign/components/submission/submission.ts

Lines changed: 59 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ import {
6464
ADDON_MOD_ASSIGN_GRADED_EVENT,
6565
ADDON_MOD_ASSIGN_MANUAL_SYNCED,
6666
ADDON_MOD_ASSIGN_PAGE_NAME,
67+
ADDON_MOD_ASSIGN_SUBMISSION_REMOVED_EVENT,
6768
ADDON_MOD_ASSIGN_SUBMITTED_FOR_GRADING_EVENT,
6869
ADDON_MOD_ASSIGN_UNLIMITED_ATTEMPTS,
6970
} from '../../constants';
@@ -96,8 +97,9 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can
9697
isSubmittedForGrading = false; // Whether the submission has been submitted for grading.
9798
acceptStatement = false; // Statement accepted (for grading).
9899
feedback?: AddonModAssignSubmissionFeedbackFormatted; // The feedback.
99-
hasOffline = false; // Whether there is offline data.
100+
editedOffline = false; // Whether the submission was added or edited in offline.
100101
submittedOffline = false; // Whether it was submitted in offline.
102+
removedOffline = false; // Whether the submission was removed in offline.
101103
fromDate?: string; // Readable date when the assign started accepting submissions.
102104
currentAttempt = 0; // The current attempt number.
103105
maxAttemptsText: string; // The text for maximum attempts.
@@ -108,6 +110,7 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can
108110
membersToSubmitBlind: number[] = []; // Team members that need to submit the assignment (blindmarking).
109111
canSubmit = false; // Whether the user can submit for grading.
110112
canEdit = false; // Whether the user can edit the submission.
113+
isRemoveAvailable = false; // Whether WS to remove submission is available.
111114
submissionStatement?: string; // The submission statement.
112115
showErrorStatementEdit = false; // Whether to show an error in edit due to submission statement.
113116
showErrorStatementSubmit = false; // Whether to show an error in submit due to submission statement.
@@ -406,6 +409,47 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can
406409
);
407410
}
408411

412+
/**
413+
* Remove submisson.
414+
*/
415+
async remove(): Promise<void> {
416+
if (!this.assign || !this.userSubmission) {
417+
return;
418+
}
419+
const message = this.assign?.timelimit ?
420+
'addon.mod_assign.removesubmissionconfirmwithtimelimit' :
421+
'addon.mod_assign.removesubmissionconfirm';
422+
try {
423+
await CoreDomUtils.showDeleteConfirm(message);
424+
} catch {
425+
return;
426+
}
427+
428+
const modal = await CoreLoadings.show('core.sending', true);
429+
430+
try {
431+
const sent = await AddonModAssign.removeSubmission(this.assign, this.userSubmission);
432+
433+
if (sent) {
434+
CoreEvents.trigger(CoreEvents.ACTIVITY_DATA_SENT, { module: 'assign' });
435+
}
436+
437+
CoreEvents.trigger(
438+
ADDON_MOD_ASSIGN_SUBMISSION_REMOVED_EVENT,
439+
{
440+
assignmentId: this.assign.id,
441+
submissionId: this.userSubmission.id,
442+
userId: this.currentUserId,
443+
},
444+
CoreSites.getCurrentSiteId(),
445+
);
446+
} catch (error) {
447+
CoreDomUtils.showErrorModalDefault(error, 'Error removing submission.');
448+
} finally {
449+
modal.dismiss();
450+
}
451+
}
452+
409453
/**
410454
* Check if there's data to save (grade).
411455
*
@@ -633,13 +677,14 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can
633677
try {
634678
const submission = await AddonModAssignOffline.getSubmission(this.assign.id, this.submitId);
635679

636-
this.hasOffline = submission && submission.plugindata && Object.keys(submission.plugindata).length > 0;
637-
638-
this.submittedOffline = !!submission?.submitted;
680+
this.removedOffline = submission && Object.keys(submission.plugindata).length == 0;
681+
this.editedOffline = submission && !this.removedOffline;
682+
this.submittedOffline = !!submission?.submitted && !this.removedOffline;
639683
} catch (error) {
640684
// No offline data found.
641-
this.hasOffline = false;
685+
this.editedOffline = false;
642686
this.submittedOffline = false;
687+
this.removedOffline = false;
643688
}
644689
}
645690

@@ -821,14 +866,14 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can
821866
return;
822867
}
823868

824-
if (this.hasOffline || this.submittedOffline) {
825-
// Offline data.
869+
if (this.editedOffline || this.submittedOffline) {
870+
// Added, edited or submitted offline.
826871
this.statusTranslated = Translate.instant('core.notsent');
827872
this.statusColor = CoreIonicColorNames.WARNING;
828873
} else if (!this.assign.teamsubmission) {
829874

830875
// Single submission.
831-
if (this.userSubmission && this.userSubmission.status != this.statusNew) {
876+
if (this.userSubmission && this.userSubmission.status != this.statusNew && !this.removedOffline) {
832877
this.statusTranslated = Translate.instant('addon.mod_assign.submissionstatus_' + this.userSubmission.status);
833878
this.statusColor = AddonModAssign.getSubmissionStatusColor(this.userSubmission.status);
834879
} else {
@@ -844,10 +889,10 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can
844889
} else {
845890

846891
// Team submission.
847-
if (!status.lastattempt?.submissiongroup && this.assign.preventsubmissionnotingroup) {
892+
if (!status.lastattempt?.submissiongroup && this.assign.preventsubmissionnotingroup && !this.removedOffline) {
848893
this.statusTranslated = Translate.instant('addon.mod_assign.nosubmission');
849894
this.statusColor = AddonModAssign.getSubmissionStatusColor(AddonModAssignSubmissionStatusValues.NO_SUBMISSION);
850-
} else if (this.userSubmission && this.userSubmission.status != this.statusNew) {
895+
} else if (this.userSubmission && this.userSubmission.status != this.statusNew && !this.removedOffline) {
851896
this.statusTranslated = Translate.instant('addon.mod_assign.submissionstatus_' + this.userSubmission.status);
852897
this.statusColor = AddonModAssign.getSubmissionStatusColor(this.userSubmission.status);
853898
} else {
@@ -907,7 +952,7 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can
907952
this.courseId,
908953
acceptStatement,
909954
this.userSubmission.timemodified,
910-
this.hasOffline,
955+
this.editedOffline,
911956
);
912957

913958
// Submitted, trigger event.
@@ -1142,11 +1187,12 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can
11421187
this.assign.requiresubmissionstatement = 0;
11431188
}
11441189

1145-
this.canSubmit = !this.isSubmittedForGrading && !this.submittedOffline && (lastAttempt.cansubmit ||
1146-
(this.hasOffline && AddonModAssign.canSubmitOffline(this.assign, submissionStatus)));
1190+
this.canSubmit = !this.isSubmittedForGrading && !this.submittedOffline && !this.removedOffline &&
1191+
(lastAttempt.cansubmit || (this.editedOffline && AddonModAssign.canSubmitOffline(this.assign, submissionStatus)));
11471192

11481193
this.canEdit = !this.isSubmittedForGrading && lastAttempt.canedit &&
11491194
(!this.submittedOffline || !this.assign.submissiondrafts);
1195+
this.isRemoveAvailable = AddonModAssign.isRemoveSubmissionAvailable();
11501196

11511197
// Get submission statement if needed.
11521198
if (this.assign.requiresubmissionstatement && this.assign.submissiondrafts && this.submitId == this.currentUserId) {

src/addons/mod/assign/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export const ADDON_MOD_ASSIGN_WARN_GROUPS_OPTIONAL = 'warnoptional';
2525

2626
// Events.
2727
export const ADDON_MOD_ASSIGN_SUBMISSION_SAVED_EVENT = 'addon_mod_assign_submission_saved';
28+
export const ADDON_MOD_ASSIGN_SUBMISSION_REMOVED_EVENT = 'addon_mod_assign_submission_removed';
2829
export const ADDON_MOD_ASSIGN_SUBMITTED_FOR_GRADING_EVENT = 'addon_mod_assign_submitted_for_grading';
2930
export const ADDON_MOD_ASSIGN_GRADED_EVENT = 'addon_mod_assign_graded';
3031
export const ADDON_MOD_ASSIGN_STARTED_EVENT = 'addon_mod_assign_started';

src/addons/mod/assign/lang.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,9 @@
8282
"numwords": "{{$a}} words",
8383
"outof": "{{$a.current}} out of {{$a.total}}",
8484
"overdue": "Assignment is overdue by: {{$a}}",
85+
"removesubmission": "Remove submission",
86+
"removesubmissionconfirm": "Are you sure you want to remove your submission?",
87+
"removesubmissionconfirmwithtimelimit": "Are you sure you want to remove your submission? Please note that this will not reset your time limit.",
8588
"submission": "Submission",
8689
"submissioneditable": "Student can edit this submission",
8790
"submissionempty": "Nothing was submitted",

src/addons/mod/assign/pages/edit/edit.ts

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -231,15 +231,8 @@ export class AddonModAssignEditPage implements OnInit, OnDestroy, CanLeave {
231231
this.timeLimitEndTime = 0;
232232
}
233233

234-
try {
235-
// Check if there's any offline data for this submission.
236-
const offlineData = await AddonModAssignOffline.getSubmission(this.assign.id, this.userId);
237-
238-
this.hasOffline = offlineData?.plugindata && Object.keys(offlineData.plugindata).length > 0;
239-
} catch {
240-
// No offline data found.
241-
this.hasOffline = false;
242-
}
234+
// Check if there's any offline data for this submission.
235+
this.hasOffline = await CoreUtils.promiseWorks(AddonModAssignOffline.getSubmission(this.assign.id, this.userId));
243236

244237
CoreAnalytics.logEvent({
245238
type: CoreAnalyticsEventType.VIEW_ITEM,

src/addons/mod/assign/services/assign-helper.ts

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,12 @@ export class AddonModAssignHelperProvider {
8181
return true;
8282
}
8383

84+
if (await CoreUtils.promiseWorks(AddonModAssignOffline.getSubmission(assign.id, submission.userid))) {
85+
// Submission was saved or deleted offline, allow editing it or creating a new one.
86+
return true;
87+
}
88+
89+
// Submission was created online, check if plugins allow editing it.
8490
let canEdit = true;
8591

8692
const promises = submission.plugins
@@ -492,7 +498,7 @@ export class AddonModAssignHelperProvider {
492498
submission.manyGroups = !!participant.groups && participant.groups.length > 1;
493499
submission.noGroups = !!participant.groups && participant.groups.length == 0;
494500
if (participant.groupname) {
495-
submission.groupid = participant.groupid;
501+
submission.groupid = participant.groupid ?? 0;
496502
submission.groupname = participant.groupname;
497503
}
498504

@@ -749,18 +755,15 @@ export const AddonModAssignHelper = makeSingleton(AddonModAssignHelperProvider);
749755
/**
750756
* Assign submission with some calculated data.
751757
*/
752-
export type AddonModAssignSubmissionFormatted =
753-
Omit<AddonModAssignSubmission, 'userid'|'groupid'> & {
754-
userid?: number; // Student id.
755-
groupid?: number; // Group id.
756-
blindid?: number; // Calculated in the app. Blindid of the user that did the submission.
757-
submitid?: number; // Calculated in the app. Userid or blindid of the user that did the submission.
758-
userfullname?: string; // Calculated in the app. Full name of the user that did the submission.
759-
userprofileimageurl?: string; // Calculated in the app. Avatar of the user that did the submission.
760-
manyGroups?: boolean; // Calculated in the app. Whether the user belongs to more than 1 group.
761-
noGroups?: boolean; // Calculated in the app. Whether the user doesn't belong to any group.
762-
groupname?: string; // Calculated in the app. Name of the group the submission belongs to.
763-
};
758+
export interface AddonModAssignSubmissionFormatted extends AddonModAssignSubmission {
759+
blindid?: number; // Calculated in the app. Blindid of the user that did the submission.
760+
submitid?: number; // Calculated in the app. Userid or blindid of the user that did the submission.
761+
userfullname?: string; // Calculated in the app. Full name of the user that did the submission.
762+
userprofileimageurl?: string; // Calculated in the app. Avatar of the user that did the submission.
763+
manyGroups?: boolean; // Calculated in the app. Whether the user belongs to more than 1 group.
764+
noGroups?: boolean; // Calculated in the app. Whether the user doesn't belong to any group.
765+
groupname?: string; // Calculated in the app. Name of the group the submission belongs to.
766+
}
764767

765768
/**
766769
* Assignment plugin config.

0 commit comments

Comments
 (0)